GROUP BY can only group what's there — quiet days aren't rows, so charts skip them, moving averages contract, and 'days since last sale' becomes uncomputable. The spine inverts the direction: start from time itself (a generated series or a calendar table), attach data where it exists, COALESCE the rest. The COALESCE is the sum_empty_null decision wearing dates; the spine is also the honest enabler for ROWS-frame rolling metrics over gapped data.
SELECT strftime(d, '%m-%d') AS day, SUM(amount)AS total FROM fees GROUP BY d ORDER BY d;
SELECT strftime(t.g, '%m-%d') AS day, COALESCE(SUM(f.amount), 0) AS total FROM generate_series( DATE '2024-02-01', DATE '2024-02-04', INTERVAL 1 DAY ) AS t (g) LEFT JOIN fees f ON f.d = t.g GROUP BY t.g ORDER BY t.g;
The timeline skipped the quiet days — a bare GROUP BY only produces rows for dates that have data, so three zero-enrolment days simply vanished until a generated spine LEFT JOINed them back.
generate_series (or a persistent calendar table — the production habit) + LEFT JOIN + COALESCE.