Home/Data Analyst/SQL Intermediate

DATE_TRUNC Purpose

DATE_TRUNC Purpose

Technical Explanation

DATE_TRUNC truncates a timestamp or date to a specified precision (year, month, day, hour, etc.), effectively rounding it down to the beginning of that unit. It's essential for aggregating data by time periods.

Syntax

DATE_TRUNC('unit', timestamp_or_date)

Available Units

Unit Truncates To Example
century Start of century 2026-01-01
decade Start of decade 2020-01-01
year January 1st 2026-01-01
quarter First day of quarter 2026-01-01
month First day of month 2026-04-01
week First day of week (Monday) 2026-03-30
day Midnight of that day 2026-04-04
hour Top of the hour 2026-04-04 14:00:00
minute Top of the minute 2026-04-04 14:30:00
second The exact second 2026-04-04 14:30:45

Code Examples

Using the CatCafe dataset:

-- Table: orders
-- id, customer_id, cat_id, order_date, total_amount, status

-- Monthly revenue aggregation
SELECT
    DATE_TRUNC('month', order_date) as month,
    COUNT(*) as order_count,
    SUM(total_amount) as revenue
FROM orders
WHERE status = 'completed'
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;

-- Weekly trends (ISO week)
SELECT
    DATE_TRUNC('week', order_date) as week_start,
    COUNT(*) as order_count,
    SUM(total_amount) as revenue
FROM orders
WHERE status = 'completed'
GROUP BY DATE_TRUNC('week', order_date)
ORDER BY week_start;

-- Quarter analysis
SELECT
    DATE_TRUNC('quarter', order_date) as quarter,
    COUNT(DISTINCT customer_id) as unique_customers,
    SUM(total_amount) as revenue,
    AVG(total_amount) as avg_order_value
FROM orders
WHERE status = 'completed'
GROUP BY DATE_TRUNC('quarter', order_date)
ORDER BY quarter;

-- Daily with hour breakdown
SELECT
    DATE_TRUNC('hour', order_date) as hour_bucket,
    COUNT(*) as orders
FROM orders
WHERE status = 'completed'
GROUP BY DATE_TRUNC('hour', order_date)
ORDER BY hour_bucket;

-- Comparing periods: This month vs same month last year
SELECT
    DATE_TRUNC('month', order_date) as month,
    SUM(total_amount) as revenue
FROM orders
WHERE status = 'completed'
    AND order_date >= DATE_TRUNC('year', CURRENT_DATE) - INTERVAL '1 year'
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;

-- Time-based cohort: Monthly cohorts (more in Cohort Analysis topic)
SELECT
    DATE_TRUNC('month', order_date) as order_month,
    COUNT(DISTINCT customer_id) as cohort_size,
    SUM(total_amount) as cohort_revenue
FROM orders
WHERE status = 'completed'
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY order_month;

The Cat Analogy

Think of DATE_TRUNC like a cat's age milestone tracker:

Instead of saying "Whiskers is 847 days old," you truncate:

DATE_TRUNC('year') = "Whiskers is 2 years old" (born in 2024) DATE_TRUNC('month') = "Whiskers is 27 months old" (truncated to month) DATE_TRUNC('day') = "Whiskers was born on 2024-02-15"

For analysis:

Without trunc: Every single day is unique (365 buckets)
DATE_TRUNC('month'): 12 buckets (Jan-Dec)
DATE_TRUNC('quarter'): 4 buckets (Q1-Q4)
DATE_TRUNC('year'): 1 bucket (All year)

You're grouping time into meaningful periods, not destroying the exact moment, just truncating the precision.


Common Pitfalls

Inconsistent Truncation

-- Mixing 'month' and 'month' can still cause issues if timezone differs
-- Always use DATE_TRUNC consistently in GROUP BY and WHERE
SELECT
    DATE_TRUNC('month', order_date) as month,
    SUM(total_amount)
FROM orders
GROUP BY order_date;  -- OOPS: grouping by original column, not truncated!
-- Use:
GROUP BY DATE_TRUNC('month', order_date);

Week Truncation Start Day

-- PostgreSQL: Week starts on Monday (ISO 8601)
-- Some databases: Week starts on Sunday
-- Be explicit about your intent if week matters:
SELECT
    DATE_TRUNC('week', order_date) as week_start,
    ...
FROM orders;
-- If you need Sunday starts, check your database's defaults

Exercises

Exercise 1

Write a query showing monthly revenue for 2024, ordered chronologically.

Exercise 2

What does DATE_TRUNC('year', '2026-06-15') return?

Exercise 3

Write a query showing which day of the week (Monday, Tuesday, etc.) has the highest average revenue.

Exercise 4

Write a query to compare Q1 vs Q2 of 2024 revenue.

Exercise 5

Why might you use DATE_TRUNC instead of just GROUP BY date_column?


Key Takeaways

  • DATE_TRUNC rounds a date/time down to a specified unit
  • Essential for aggregating data by time periods
  • Common units: year, quarter, month, week, day, hour
  • Always use the same DATE_TRUNC expression in SELECT and GROUP BY
  • Week truncation start day varies by database (Monday in PostgreSQL)
  • Combine with COUNT, SUM, AVG for time-based analytics