UPDATE:
Since you are not interested in any “missing” lines, I am also going to assume (possibly erroneously) that you are not worried that the query may return rows for periods that are not from 7AM to 12AM. This query will return the result set you specified:
SELECT (HOUR(r.time)-7)*2+(MINUTE(r.time) DIV 30) AS i , SUM(r.subtotal) AS sum_subtotal FROM Receipts r GROUP BY i ORDER BY i
Returns the period (i) index obtained from an expression that references the time column. For the best performance of this query, you probably want to have a coverage index available, for example:
ON Receipts(`time`,`subtotal`)
If you are going to include the equality predicate in the date column (which does not appear in your solution, but which appears in the solution of the "selected" answer, then it would be nice to have this column as the leading index in the "covering" index.
ON Receipts(`date`,`time`,`subtotal`)
If you want you to not return rows for periods before 7AM, you can simply add the HAVING i >= 0 to the query. (Lines for periods up to 7AM will generate a negative number for i.)
SELECT (HOUR(r.time)-7)*2+(MINUTE(r.time) DIV 30) AS i , SUM(r.subtotal) AS sum_subtotal FROM Receipts r GROUP BY i HAVING i >= 0 ORDER BY i
BEFORE:
I suggested that you want the result set to be similar to the one you are returning now, but in one fell swoop. This query will return the same 33 rows that you are currently extracting, but with an additional column defining the period (0 - 33). This is as close as possible to your current solution, which I could get:
SELECT ti , IFNULL(SUM(r.subtotal),0) AS sum_subtotal FROM (SELECT (d1.i + d2.i + d4.i + d8.i + d16.i + d32.i) AS i , ADDTIME('07:00:00',SEC_TO_TIME((d1.i+d2.i+d4.i+d8.i+d16.i+d32.i)*1800)) AS b_time , ADDTIME('07:30:00',SEC_TO_TIME((d1.i+d2.i+d4.i+d8.i+d16.i+d32.i)*1800)) AS e_time FROM (SELECT 0 i UNION ALL SELECT 1) d1 CROSS JOIN (SELECT 0 i UNION ALL SELECT 2) d2 CROSS JOIN (SELECT 0 i UNION ALL SELECT 4) d4 CROSS JOIN (SELECT 0 i UNION ALL SELECT 8) d8 CROSS JOIN (SELECT 0 i UNION ALL SELECT 16) d16 CROSS JOIN (SELECT 0 i UNION ALL SELECT 32) d32 HAVING i <= 33 ) t LEFT JOIN Receipts r ON r.time >= t.b_time AND r.time < t.e_time GROUP BY ti ORDER BY ti
Some important notes:
It looks like your current solution may be the “missing” lines from the receipts whenever the seconds are exactly “59” or “00”.
It also looks like you are not associated with a date component, you only get one value for all dates. (Perhaps I misunderstood this.) If so, separating the DATE and TIME columns helps in this, because you can reference the column unchanged in your query.
It's easy to add a WHERE clause in the date column. for example, to get interim reports in just one day, for example. add a WHERE clause before GROUP BY .
WHERE r.date = '2011-09-10'
Coverage Index ON Receipts(time,subtotal) (if you do not already have a coverage index) can help in performance. (If you include an equality predicate in a date column (as in the WHERE clause above, the most suitable coverage index is probably ON Receipts(date,time,subtotal) .
I made the assumption that the time column has a TIME data type. (If this is not the case, then probably a slight adjustment of the request is required (in the embedded view with an alias like t ) so that the data type of the (received) b_time and e_time columns matches the time data type in the receipts.
Some of the proposed solutions in other answers are not guaranteed to return 33 lines if there are no lines in receipts over a period of time. Missing lines may not be a problem for you, but it is a common problem with timers and time period data.
I made the assumption that you would rather have a 33-string return guarantee. The above query returns a total value of zero if no rows were found according to the time period. (I note that your current solution will return NULL in this case. I sent and wrapped this SUM aggregate to the IFNULL function, so that it will return 0 when SUM is NULL.)
So, an inline query with the alias t is an ugly mess, but it works fast. What he does is generate 33 rows with various integer values from 0 to 33. At the same time, he gets the “start time” and “end time”, which will be used to “match” each period with the time column in the Receipts table.
We try not to wrap the time column from the Receipts table in any functions, but refer only to the bare column. And we want us to not have any implicit conversion (so we want the b_time and e__time data types to match. ADDTIME and SEC_TO_TIME function as time returning data types. (We can't get around the execution of matching and GROUP BY operations.)
The "end time" value for this last period is returned as "24:00:00", and we verify that this is the right time to match by running this test:
SELECT MAKETIME(23,59,59) < MAKETIME(24,0,0)
which is successful (returns 1), so we are good there.
Output columns ( t.b_time and t.e_time ) can also be included in the result set, but they are not needed to create your array and are most likely more efficient if you do not include them.
One final note: for optimal performance, it may be useful to load the inline view named t into the actual table (the temporary table will be fine.), And then you can reference the table instead of the inline view. The advantage of this is that you can create an index in this table.