Проблема: групування за датою повертає лише дні, у які були дані. Для графіка «замовлень по днях» дні без замовлень просто зникають, і лінія графіка бреше.
SELECT DATE(created_at) AS day, COUNT(*) FROM orders GROUP BY day;
-- 2026-10-01: 15, 2026-10-03: 9 (2 жовтня пропущено, хоча там 0)
Рішення - згенерувати повний календар і приєднати до нього дані:
WITH RECURSIVE calendar AS (
SELECT DATE('2026-10-01') AS day
UNION ALL
SELECT day + INTERVAL 1 DAY
FROM calendar
WHERE day < '2026-10-31'
)
SELECT c.day, COUNT(o.id) AS orders
FROM calendar c
LEFT JOIN orders o
ON o.created_at >= c.day
AND o.created_at < c.day + INTERVAL 1 DAY
GROUP BY c.day
ORDER BY c.day;
Ключові деталі:
LEFT JOINвід календаря, а не від даних - щоб дні без замовлень лишилися.COUNT(o.id), а неCOUNT(*)-COUNT(*)порахує рядок календаря й дасть 1 замість 0.- Умова з'єднання діапазоном, а не
DATE(o.created_at) = c.day- так працює індекс наcreated_at. - Ліміт рекурсії:
cte_max_recursion_depthза замовчуванням 1000. Для календаря на кілька років його збільшують для сесії.
Часові пояси: «день» залежить від поясу. Якщо created_at зберігається в UTC, а звіт - за київським часом, межі днів треба зсунути (CONVERT_TZ або обчислені межі), інакше замовлення о 01:00 за Києвом потраплять у попередній день.
Альтернативи:
- Постійна таблиця-календар з датами на роки вперед - корисна, якщо звітів багато, і в неї можна додати атрибути (вихідні, свята, номер тижня).
- Заповнення пропусків у застосунку - згенерувати діапазон дат у PHP (
CarbonPeriod) і злити з результатом запиту.
У PostgreSQL для цього є generate_series().