Domanda

This feels simple, but I can't find an answer anywhere.
I'm trying to run a query by time of day for each hour. So I'm doing a Group By on the hour part, but not all hours have data, so there are some gaps. I'd like to display every hour, regardless of whether or not there's data.

Here's a sample query:

SELECT DATEPART(HOUR, DATEADD(HH,-5, CreationDate)) As Hour,
       COUNT(*) AS Count
FROM Comments
WHERE UserId = ##UserId##
GROUP BY DATEPART(HOUR, DATEADD(HH,-5, CreationDate))

My thought was to Join to a table that already had numbers 1 through 24 so that the incoming data would get put in it's place.

Can I do this with a CTE?

WITH Hours AS (
   SELECT i As Hour    --Not Sure on this
   FROM [1,2,3...24]), --Not Sure on this
CommentTimes AS (
   SELECT DATEPART(HOUR, DATEADD(HH,-5, CreationDate)) AS Hour,
          COUNT(*) AS Count
   FROM Comments
   WHERE UserId = ##UserId##
   GROUP BY DATEPART(HOUR, DATEADD(HH,-5, CreationDate))
)
SELECT h.Hour, c.Count
FROM Hours h
JOIN CommentTimes c ON h.Hour = c.Hour

###Here's a sample Query From Stack Exchange Data Explorer

È stato utile?

Soluzione

You can use a recursive query to build up a table of whatever numbers you want. Here we stop at 24. Then left join that to your comments to ensure every hour is represented. You can turn these into times easily if you wanted. I also changed your use of hour as a column name as it is a keyword.

;with dayHours as (
    select 1 as HourValue
    union all select hourvalue + 1
    from dayHours
    where hourValue < 24
)
,
CommentTimes As (
       SELECT DATEPART(HOUR, DATEADD(HH,-5, CreationDate)) As HourValue,
              COUNT(*) AS Count
       FROM Comments
       WHERE UserId = ##UserId##
       GROUP BY DATEPART(HOUR, DATEADD(HH,-5, CreationDate)))
SELECT h.Hour, c.Count
FROM dayHours h
left JOIN CommentTimes c ON h.HourValue = c.HourValue

Altri suggerimenti

You can use a table value constructor:

with hours as (
    SELECT hr
    FROM  (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) AS b(hr) 
)
etc..

You can also use a permanent auxilliary numbers table.

http://dataeducation.com/you-require-a-numbers-table/

Use a recursive CTE to generate the hours:

with hours as (
      select 1 as hour
      union all
      select hour + 1
      from hours
      where hour < 24
     )
. . .

Then your full query needs a left outer join:

with hours as (
      select 1 as hour
      union all
      select hour + 1
      from hours
      where hour < 24
     )
     CommentTimes As (
      SELECT DATEPART(HOUR, DATEADD(HH,-5, CreationDate)) As Hour,
             COUNT(*) AS Count
      FROM Comments
      WHERE UserId = ##UserId##
      GROUP BY DATEPART(HOUR, DATEADD(HH,-5, CreationDate))
     )
SELECT h.Hour, c.Count
FROM Hours h LEFT OUTER JOIN
     CommentTimes c
     ON h.Hour = c.Hour;

Below is demo without using recursive CTE for sql-server

select h.hour ,c.count
from (
    select top 24 number + 1 as hour from master..spt_values 
    where type = 'P'
) h 
left join (
    select datepart(hour, creationdate) as hour,count(1) count
    from comments
    where userid = '9131476'
    group by datepart(hour, creationdate)
) c on h.hour = c.hour
order by h.hour;

online demo link : consecutive number query demo - Stack Exchange Data Explorer

The basic idea is correct, but you will want to perform a left join instead of a standard join. The reason for the left join is because you want the answers from the left-hand side.

With respect to how to create the original hours table, you can either directly create it with something like:

SELECT 1 as hour
UNION ALL
SELECT 2 as hour
  ...
UNION ALL
SELECT 24 as hour

or, you can create a permanent table populated with these values. (I do not recall immediately on SqlServer if there is a better way to do this, or if selecting a value but not from a table is allowed. On Oracle, you could select from the built-in table 'dual' which is a table containing a single row).

As a more general abstraction of this issue, you can create consecutive numbers Brad and Gordon have suggested with a recursive CTE like this:

WITH Numbers AS (
    SELECT 1 AS Number
    UNION ALL SELECT Number + 1
    FROM Numbers
    WHERE Number < 1000
)
SELECT * FROM Numbers
OPTION (MaxRecursion 0)

As a note, if you plan to go over 100 numbers, you'll need to add OPTION (MaxRecursion 0) to the end of your query to prevent the error The maximum recursion 100 has been exhausted before statement completion

This technique can commonly be seen when populating or using a Tally Table in TSQL

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top