We are building a query to count the number of events per hour, per day. Most days there are hours that do not have any activity and therefore where the query is run the count of activities per hour show up but there are gaps and the query excludes these. We still want to show the hours that do not have activity and display a zero so that zero value can then be charted. The query we using looks like this …
select datepart(Year, dev_time) as Year,
datepart(Month, dev_time) as Month,
datepart(Day, dev_time) as Day,
datepart(Hour, dev_time) as Hour,
count(tdm_msg) as Total_ACTIVITES
from TCKT_ACT
where tdm_msg = ‘4162′ and dev_time >= DATEADD(day, - 1, GETDATE())
group by datepart(Year, dev_time) ,
datepart(Month, dev_time) ,
datepart(Day, dev_time),
datepart(Hour, dev_time)
order by datepart(Year, dev_time) asc,
datepart(Month, dev_time) asc,
datepart(Day, dev_time) asc,
datepart(Hour, dev_time) asc
It sounds like you could use a "left outer join" using another table that has the numbers 1 through 24 in it...
First I created a table function based on the recursive common table query described by Dave Markle (thanks for showing me this Dave!). This is extremely sweet because I only have to make the function once and I can use it for analysing any intervals.
So if you do a select from that function all by itself you get a table of time intervals like this:
fn_daterange('12/14/2008 10:00:00', '12/14/2008 20:00:00', '01:00:00' )
returns:
Then I made a sample table of event data:
Then I hooked them together with a LEFT OUTER JOIN like so:
HOLY CRAP that is sweet - I can use this for all kinds of analysis at work! :-)
Thanks Fred for the question and Dave for the info on common table queries!
Ron
You are going to somehow need a table of days and hours, and then you will have to do an outer join between that table and your query. Here's how I would do it. Note that this solution will only work in SQL Server 2005 and 2008. If you don't have those platforms, you'll have to actually create a table of times in your database from which you can join off of:
Note that the WITH statement at the top is called a recursive common table expression, and is a good way of generating sequential tables with relatively small numbers of elements, like you have here.
We had a similar problem with some performance monitoring software but, being in a DB2/z mainframe shop, we're dead set against having to do SQL gymnastics to get those sort of results. SQL queries that perform 'functions' on every row they retrieve are notoriously unscalable and the DBAs would have a field day laughing at us if we tried to use them.
Instead, we found it easier to refactor the database schema to include a count of events in each row (apparently our DBAs don't mind using more disk space, just more CPU grunt). In your case, that would be adding a column called
tdm_quant
which you would set to 1 for every row that you insert (i.e., each event).Then the fifth field of your query changes from
count(tdm_msg)
tosum(tdm_quant)
which will achieve the same result.In addition to that you can insert a special record (once an hour, or 24 of them at the start of each day, or populate the entire years worth on January 1 if you wish) where the
tdm_quant
field is set to zero. Being zero, these records will have no effect on thesum(tdm_quant)
but you will get your desired behaviour, a row returned for every hour of the day which will have zero asTotal_ACTIVITIES
where no events occurred in that hour.The rest of your query will not need to change.
The basic answer here involves a left outer join (LOJ), and an explicit
COUNT(column)
since that does not count nulls but COUNT(*) counts all rows. The hard part is generating a table against which to do the LOJ. The WITH clause and recursive solution will work in a number of DBMS (MS SQL Server, apparently, and almost certainly DB2 -- probably others too).Many DBMS support temporary tables and stored procedures; the combination could be used to populate a table with an appropriate set of values for the date/time field, and then do the LOJ against that table (or, more precisely, FROM temp_table LEFT OUTER JOIN main_table ...). Not as neat and tidy, but works most places.