SQL - DATEPART() / EXTRACT() - Get Part of Date

Date manipulation sits at the core of nearly every reporting system. You need to group sales by quarter, filter orders placed on weekends, or calculate how many years someone has been a customer....

Key Insights

  • DATEPART() is SQL Server’s proprietary function while EXTRACT() follows the SQL standard and works across PostgreSQL, MySQL, and Oracle—learn both syntaxes to write portable code
  • Using date extraction functions in WHERE clauses prevents index usage; restructure queries to use date range comparisons for dramatically better performance
  • Week numbering varies significantly between databases and regional standards (ISO vs. US), causing subtle bugs in reporting queries if not handled explicitly

Why Extract Date Parts?

Date manipulation sits at the core of nearly every reporting system. You need to group sales by quarter, filter orders placed on weekends, or calculate how many years someone has been a customer. Rather than storing redundant columns for year, month, and day, you extract these components from a single timestamp column when needed.

The challenge? Every database implements this differently. SQL Server uses DATEPART(), while PostgreSQL, MySQL, and Oracle follow the SQL standard with EXTRACT(). Understanding both approaches lets you write queries that work across your stack.

Syntax Comparison Across Databases

Here’s how the major databases handle date part extraction:

Database Function Syntax
SQL Server DATEPART() DATEPART(part, date)
PostgreSQL EXTRACT() EXTRACT(part FROM date)
MySQL EXTRACT() EXTRACT(part FROM date)
Oracle EXTRACT() EXTRACT(part FROM date)

The syntax difference is small but matters. SQL Server uses comma-separated arguments while the standard uses FROM keyword syntax.

-- SQL Server: Get the month from an order date
SELECT DATEPART(MONTH, order_date) AS order_month
FROM orders;

-- PostgreSQL / MySQL / Oracle: Same query using EXTRACT
SELECT EXTRACT(MONTH FROM order_date) AS order_month
FROM orders;

MySQL also supports DATEPART()-style functions like MONTH(), YEAR(), and DAY() as shortcuts:

-- MySQL shorthand functions
SELECT 
    YEAR(order_date) AS order_year,
    MONTH(order_date) AS order_month,
    DAY(order_date) AS order_day
FROM orders;

SQL Server offers these same shorthand functions, making simple extractions more readable. Use them when you only need one component.

Available Date Parts

The date parts you can extract vary slightly by database, but the core set remains consistent:

Part SQL Server PostgreSQL/MySQL Description
Year YEAR YEAR 4-digit year
Quarter QUARTER QUARTER 1-4
Month MONTH MONTH 1-12
Day DAY DAY 1-31
Week WEEK WEEK Week of year (varies by standard)
Day of Year DAYOFYEAR DOY 1-366
Weekday WEEKDAY DOW Day of week (numbering varies)
Hour HOUR HOUR 0-23
Minute MINUTE MINUTE 0-59
Second SECOND SECOND 0-59

SQL Server includes additional parts like ISO_WEEK for ISO 8601 week numbering and MICROSECOND for sub-second precision. PostgreSQL’s EXTRACT() can pull EPOCH (Unix timestamp), TIMEZONE, and fractional seconds.

-- SQL Server: Extract multiple parts from a timestamp
SELECT 
    order_timestamp,
    DATEPART(YEAR, order_timestamp) AS year,
    DATEPART(QUARTER, order_timestamp) AS quarter,
    DATEPART(MONTH, order_timestamp) AS month,
    DATEPART(DAY, order_timestamp) AS day,
    DATEPART(HOUR, order_timestamp) AS hour,
    DATEPART(MINUTE, order_timestamp) AS minute,
    DATEPART(WEEKDAY, order_timestamp) AS weekday
FROM orders
WHERE order_id = 12345;

-- PostgreSQL: Same extraction
SELECT 
    order_timestamp,
    EXTRACT(YEAR FROM order_timestamp) AS year,
    EXTRACT(QUARTER FROM order_timestamp) AS quarter,
    EXTRACT(MONTH FROM order_timestamp) AS month,
    EXTRACT(DAY FROM order_timestamp) AS day,
    EXTRACT(HOUR FROM order_timestamp) AS hour,
    EXTRACT(MINUTE FROM order_timestamp) AS minute,
    EXTRACT(DOW FROM order_timestamp) AS weekday
FROM orders
WHERE order_id = 12345;

Practical Query Examples

Grouping Sales by Month and Year

The most common use case: aggregate data by time periods for reporting dashboards.

-- SQL Server: Monthly revenue report
SELECT 
    DATEPART(YEAR, order_date) AS year,
    DATEPART(MONTH, order_date) AS month,
    COUNT(*) AS order_count,
    SUM(total_amount) AS revenue
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY 
    DATEPART(YEAR, order_date),
    DATEPART(MONTH, order_date)
ORDER BY year, month;

-- PostgreSQL: Same report
SELECT 
    EXTRACT(YEAR FROM order_date) AS year,
    EXTRACT(MONTH FROM order_date) AS month,
    COUNT(*) AS order_count,
    SUM(total_amount) AS revenue
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY 
    EXTRACT(YEAR FROM order_date),
    EXTRACT(MONTH FROM order_date)
ORDER BY year, month;

Filtering by Weekday

Find all orders placed on weekends for a promotion analysis:

-- SQL Server: Weekend orders (assuming Sunday=1, Saturday=7 with default DATEFIRST)
SELECT *
FROM orders
WHERE DATEPART(WEEKDAY, order_date) IN (1, 7);

-- PostgreSQL: Weekend orders (Sunday=0, Saturday=6)
SELECT *
FROM orders
WHERE EXTRACT(DOW FROM order_date) IN (0, 6);

Calculating Customer Tenure

Determine how many complete years a customer has been active:

-- SQL Server: Customer tenure in years
SELECT 
    customer_id,
    signup_date,
    DATEPART(YEAR, GETDATE()) - DATEPART(YEAR, signup_date) AS years_as_customer
FROM customers;

-- PostgreSQL: More accurate using AGE function, but EXTRACT works too
SELECT 
    customer_id,
    signup_date,
    EXTRACT(YEAR FROM AGE(CURRENT_DATE, signup_date)) AS years_as_customer
FROM customers;

Performance Considerations

Here’s where many developers get burned. Using DATEPART() or EXTRACT() in a WHERE clause wraps your column in a function, making the query non-sargable. The database cannot use an index on that column.

-- BAD: Non-sargable query - cannot use index on order_date
SELECT *
FROM orders
WHERE DATEPART(YEAR, order_date) = 2024
  AND DATEPART(MONTH, order_date) = 6;

-- GOOD: Sargable query - uses index on order_date
SELECT *
FROM orders
WHERE order_date >= '2024-06-01'
  AND order_date < '2024-07-01';

The difference in execution time can be orders of magnitude on large tables. I’ve seen queries drop from 30 seconds to 50 milliseconds simply by restructuring the WHERE clause.

For frequently filtered date components, consider computed columns with indexes:

-- SQL Server: Add a persisted computed column
ALTER TABLE orders
ADD order_year AS DATEPART(YEAR, order_date) PERSISTED;

CREATE INDEX IX_orders_year ON orders(order_year);

-- Now this query uses the index
SELECT * FROM orders WHERE order_year = 2024;

PostgreSQL achieves the same with expression indexes:

-- PostgreSQL: Expression index
CREATE INDEX idx_orders_year ON orders (EXTRACT(YEAR FROM order_date));

Common Pitfalls and Edge Cases

Week Numbering Chaos

Week-of-year calculations cause more bugs than any other date extraction. ISO 8601 defines week 1 as the week containing the first Thursday of the year. US standard often uses the week containing January 1. Some systems start weeks on Sunday, others on Monday.

-- SQL Server: Compare standard week vs ISO week
SELECT 
    order_date,
    DATEPART(WEEK, order_date) AS us_week,
    DATEPART(ISO_WEEK, order_date) AS iso_week
FROM orders
WHERE order_date BETWEEN '2024-12-28' AND '2025-01-05';

-- PostgreSQL: ISO week only
SELECT 
    order_date,
    EXTRACT(WEEK FROM order_date) AS iso_week
FROM orders
WHERE order_date BETWEEN '2024-12-28' AND '2025-01-05';

For December 30, 2024, you might get week 53 using one standard and week 1 using another. Document your choice explicitly in reporting code.

Weekday Numbering

SQL Server’s DATEPART(WEEKDAY, ...) returns values based on the @@DATEFIRST setting. The default makes Sunday = 1, but this varies by server configuration. PostgreSQL’s DOW always returns Sunday = 0.

-- SQL Server: Check and set first day of week
SELECT @@DATEFIRST;  -- 7 = Monday, 1 = Sunday (US default)

SET DATEFIRST 1;  -- Set Monday as first day of week

-- PostgreSQL: Consistent behavior, but different numbering
-- DOW: Sunday=0, Monday=1, ..., Saturday=6
-- ISODOW: Monday=1, ..., Sunday=7
SELECT 
    EXTRACT(DOW FROM order_date) AS dow,
    EXTRACT(ISODOW FROM order_date) AS isodow
FROM orders;

Timezone Handling

EXTRACT() operates on the timestamp as stored. If you store UTC and need to extract the hour in a user’s local timezone, convert first:

-- PostgreSQL: Extract hour in specific timezone
SELECT 
    EXTRACT(HOUR FROM created_at AT TIME ZONE 'America/New_York') AS local_hour
FROM events;

Quick Reference Cheat Sheet

Task SQL Server PostgreSQL/MySQL
Get year DATEPART(YEAR, col) or YEAR(col) EXTRACT(YEAR FROM col)
Get month DATEPART(MONTH, col) or MONTH(col) EXTRACT(MONTH FROM col)
Get day DATEPART(DAY, col) or DAY(col) EXTRACT(DAY FROM col)
Get quarter DATEPART(QUARTER, col) EXTRACT(QUARTER FROM col)
Get ISO week DATEPART(ISO_WEEK, col) EXTRACT(WEEK FROM col)
Get day of week DATEPART(WEEKDAY, col) EXTRACT(DOW FROM col)
Get hour DATEPART(HOUR, col) EXTRACT(HOUR FROM col)

Use dedicated functions (YEAR(), MONTH()) when available for readability. Reserve DATEPART()/EXTRACT() for less common parts like quarter or week. Always use date range comparisons in WHERE clauses instead of extracting parts for filtering. Your database will thank you with faster queries.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.