SELECT
3600 * FLOOR(`time` / 3600) as `interval`,
COUNT(`id`) AS `count`
FROM
`visitor_stat`
WHERE
`date` >= $sTime
AND `date <= $eTime
GROUP BY
1
ORDER BY
`interval`
3600 * FLOOR(`time` / 3600) as `interval`,
Как можно добавить тот самый пустой элемент, в примере это 15:00 со значением 0?
WITH RECURSIVE `cte` (`from`, `to`) AS (
SELECT CAST('00:00' AS TIME), CAST('01:00' AS TIME)
UNION SELECT `to`, `to` + INTERVAL 1 HOUR
FROM `cte`
WHERE `to` < '24:00'
)
SELECT `cte`.`from` AS `interval`, COUNT(*) AS `count`
FROM `cte`
LEFT JOIN `visitor_stat` AS `v`
ON `v`.`time` >= `cte`.`from` AND `v`.`time` < `cte`.`to`
WHERE `date` >= :sTime AND `date <= :eTime
GROUP BY `cte`.`from`
ORDER BY `cte`.`from`
WHERE `to` < '24:00'
предполагает, что контекст выполнения - время. Оно, конечно, так, но на неявные установки лучше не полагаться, и указать тип данных литерала явно: WHERE `to` < TIME '24:00'
.ON `v`.`time` BETWEEN `cte`.`from` AND `cte`.`to`
посчитает запись дважды. Следует использовать два неравенства, одно строгое, второе нет: ON `v`.`time` >= `cte`.`from` AND `v`.`time` < `cte`.`to`
. SELECT `t`.`from` AS `interval`, COUNT(*) AS `count`
FROM (
SELECT CAST('00:00' AS TIME) AS `from`, CAST('01:00' AS TIME) AS `to`
UNION ALL SELECT CAST('01:00' AS TIME), CAST('02:00' AS TIME)
...
UNION ALL SELECT CAST('23:00' AS TIME), CAST('24:00' AS TIME)
) AS `t`
LEFT JOIN `visitor_stat` AS `v`
ON `v`.`time` >= `t`.`from` AND `v`.`time` < `t`.`to`
WHERE `date` >= :sTime AND `date <= :eTime
GROUP BY `t`.`from`
ORDER BY `t`.`from`
у меня MySQL, версия 7.4.30
SELECT VERSION();
и выложите полный ответ сервера.в обоих вариантах выходит ошибка
5.7.36
...
JOIN ( SELECT 0 AS `hour` UNION ALL SELECT 1 UNION ALL SELECT 2 .. UNION ALL SELECT 23) AS hourslist
...
...
LEFT JOIN visitor_stat ON HOUR(visitor_stat.`time`) = hourslist.`hour`
...
SET time_zone = 'UTC';
SELECT `id`, FROM_UNIXTIME(`time`), FROM_UNIXTIME(7200 * FLOOR(`time` / 7200))
FROM `visitor_stat`
| id | FROM_UNIXTIME(`time`) | interval |
| --- | --------------------- | ------------------- |
| 9 | 2022-09-03 07:00:57 | 2022-09-03 06:00:00 |
| 14 | 2022-09-03 15:17:51 | 2022-09-03 14:00:00 |
| 15 | 2022-09-03 21:47:50 | 2022-09-03 20:00:00 |
| 17 | 2022-09-03 23:16:40 | 2022-09-03 22:00:00 |
Всё красиво, всё совпадает. Теперь поставим другую зону.SET time_zone = 'Europe/Moscow';
SELECT `id`, FROM_UNIXTIME(`time`), FROM_UNIXTIME(7200 * FLOOR(`time` / 7200))
FROM `visitor_stat`
| id | FROM_UNIXTIME(`time`) | interval |
| --- | --------------------- | ------------------- |
| 9 | 2022-09-03 10:00:57 | 2022-09-03 09:00:00 |
| 14 | 2022-09-03 18:17:51 | 2022-09-03 17:00:00 |
| 15 | 2022-09-04 00:47:50 | 2022-09-03 23:00:00 |
| 17 | 2022-09-04 02:16:40 | 2022-09-04 01:00:00 |
Что мы видим? Поскольку зона у нас +03:00, то всё время сдвинулось на нечётное количество часов. Значит работать надо не с математическими операциями, а с функциями времени.SET time_zone = 'Europe/Moscow';
SELECT CAST(CONCAT(`t`.`hour`, ':00') AS TIME) AS `interval`, COUNT(`id`) as `count`
FROM (
SELECT 0 AS `hour`
UNION ALL SELECT 2
UNION ALL SELECT 4
UNION ALL SELECT 6
UNION ALL SELECT 8
UNION ALL SELECT 10
UNION ALL SELECT 12
UNION ALL SELECT 14
UNION ALL SELECT 16
UNION ALL SELECT 18
UNION ALL SELECT 20
UNION ALL SELECT 22
) AS `t`
LEFT JOIN `visitor_stat` AS `v`
ON HOUR(FROM_UNIXTIME(`v`.`time`)) BETWEEN `t`.`hour` AND `t`.`hour` + 1
GROUP BY `t`.`hour`
| interval | count |
| -------- | ----- |
| 00:00:00 | 2 |
| 02:00:00 | 2 |
| 04:00:00 | 0 |
| 06:00:00 | 0 |
| 08:00:00 | 0 |
| 10:00:00 | 5 |
| 12:00:00 | 0 |
| 14:00:00 | 0 |
| 16:00:00 | 0 |
| 18:00:00 | 1 |
| 20:00:00 | 0 |
| 22:00:00 | 0 |
Правда, запрос получится небыстрый, поскольку в условии JOIN'a функция и индекс неприменим,