Time & dates
Concept  ·  SQL & Querying

date_trunc('month', ...) groups within a month; EXTRACT(month ...) merges Januaries across years.

Both functions answer real but different questions. EXTRACT strips the calendar context: month 1 is 'Januaryness', useful for seasonality across all years. date_trunc keeps the timeline: 2025-01 is a point in history, useful for trends. The bug is answering a trend question with the seasonality tool: the 'monthly revenue' report that quietly sums every year's January. The tell: exactly 12 rows out of a multi-year table.

See it break
gigs
paid_onfee
2024-01-10100
2025-01-05150
2025-02-0860
The trap
SELECT
  EXTRACT (month
    FROM
      paid_on)AS m,
  SUM(fee) AS fees
FROM
  gigs
GROUP BY
  m
ORDER BY
  m;
The fix
SELECT
  strftime(date_trunc('month', paid_on), '%Y-%m') AS m,
  SUM(fee) AS fees
FROM
  gigs
GROUP BY
  date_trunc('month', paid_on)
ORDER BY
  1;
The trap
1250
260
The fix
2024-01100
2025-01150
2025-0260

Grouping by EXTRACT(month) merged every year's January into one row — the two Januaries summed to 250, where date_trunc keeps 2024-01 and 2025-01 as separate months.

The rule

date_trunc for timelines; EXTRACT for seasonality, labeled as such; GROUP BY (year, month) as the explicit spelling.

Shows up elsewhere
Half-open intervalsBETWEEN on timestamps — the midnight trap
Try one →Prove it in practice →