If you’re grouping by time and you don’t want gaps in your report data, you need to generate a series of time values and use it to do an outer join with your data. Prior to MySQL 8, you can do this using variables. In the following example we extracted the number of rentals per hour from the Sakila Sample Database:
-- The first line is to make the first value of statement below 0:00:00, not 1:00:00 -- LIMIT 720 gives 30 days worth of hourly values SET @n:=('2005-05-25' - INTERVAL 1 HOUR); SELECT hours.this_hour, count(rental.rental_id) FROM (SELECT (SELECT @n:= @n + INTERVAL 1 HOUR) this_hour FROM inventory LIMIT 720) hours LEFT JOIN rental ON (hours.this_hour=date_format(rental.rental_date,'%Y-%m-%d %H:00:00')) GROUP BY hours.this_hour;
There are serious limitations with the above method. For one thing, you may ask what does the
inventory table have to do with rentals? It does not have anything to do with it directly. The
inventory table got chosen because it has a few thousand rows and we needed one that has at least 720 to generate our series of time values. In reality any table with a large number of rows can be used for this. But what if there is none available?
Fortunately, starting with MySQL 8, you can instead use a common table expression:
WITH RECURSIVE my_hours AS ( SELECT 0 as inc UNION ALL SELECT 1+inc FROM my_hours WHERE inc<=720 ) SELECT hours.this_hour, count(rental.rental_id) FROM (SELECT '2005-05-25' + interval inc hour as this_hour FROM my_hours) as hours LEFT JOIN rental ON (hours.this_hour=date_format(rental.rental_date,'%Y-%m-%d %H:00:00')) GROUP BY hours.this_hour;