Date/Time Data Types and Extraction
Progress Tracking
Log in to save this lesson and continue from where you left off.
Working with Time
Dates and timestamps are everywhere: signup times, order dates, event logs, and session starts. If you’re doing any kind of analytics — retention, cohorts, time-series trends — you need to slice and dice temporal data. This module gives you the toolkit.
Fair warning: date functions are where SQL dialects diverge most. We cover all four — PostgreSQL, MySQL, SQL Server, and Oracle — throughout this module. The prose and examples use PostgreSQL syntax; the practice exercises include all dialects, so you can switch to your target database using the dialect selector above the editor.
Date/Time Data Types
Before extracting parts of dates, understand what you’re working with:
DATE— Just the date: ‘2024-03-15’ (year, month, day only)TIME— Just the time: ‘14:30:00’ (hours, minutes, seconds)TIMESTAMP/DATETIME— Both date and time: ‘2024-03-15 14:30:00’TIMESTAMPTZ— Timestamp with timezone (PostgreSQL)
Most interview questions use DATE or TIMESTAMP. If you see a column ending in ‘_at’ (created_at, updated_at), it’s almost always a timestamp.
SELECT
first_name,
joining_date,
EXTRACT(YEAR FROM joining_date) AS year,
EXTRACT(MONTH FROM joining_date) AS month,
EXTRACT(DAY FROM joining_date) AS day
FROM worker;Common Extraction Values
YEAR— The 4-digit year (2024)MONTH— Month number 1–12DAY— Day of month 1–31HOUR— Hour 0–23 (from timestamps)MINUTE— Minute 0–59DOW— Day of week (0=Sunday in PostgreSQL)WEEK— Week number of the yearQUARTER— Quarter 1–4
Dialect Comparison: Date Extraction
This is where dialects diverge significantly. If you’re interviewing somewhere specific, memorize that dialect’s syntax. Otherwise, know PostgreSQL (most common in tech) and be ready to look up the rest.
- Current date:
- PostgreSQL —
CURRENT_DATE - MySQL —
CURDATE() - MSSQL —
CAST(GETDATE() AS DATE) - Oracle —
TRUNC(SYSDATE)
- PostgreSQL —
- Current timestamp:
- PostgreSQL —
NOW() - MySQL —
NOW() - MSSQL —
GETDATE() - Oracle —
SYSTIMESTAMP
- PostgreSQL —
- Extract year:
- PostgreSQL —
EXTRACT(YEAR FROM d) - MySQL —
YEAR(d) - MSSQL —
YEAR(d) - Oracle —
EXTRACT(YEAR FROM d)
- PostgreSQL —
- Extract month:
- PostgreSQL —
EXTRACT(MONTH FROM d) - MySQL —
MONTH(d) - MSSQL —
MONTH(d) - Oracle —
EXTRACT(MONTH FROM d)
- PostgreSQL —
- Extract day:
- PostgreSQL —
EXTRACT(DAY FROM d) - MySQL —
DAY(d) - MSSQL —
DAY(d) - Oracle —
EXTRACT(DAY FROM d)
- PostgreSQL —
- Day of week:
- PostgreSQL —
EXTRACT(DOW FROM d) - MySQL —
DAYOFWEEK(d) - MSSQL —
DATEPART(dw, d) - Oracle —
TO_CHAR(d, 'D')
- PostgreSQL —
In MySQL and MSSQL, YEAR(), MONTH(), DAY() are shorter than EXTRACT(). Same result, less typing. Use what your team uses for consistency.
Filtering by Date Parts
EXTRACT shines in WHERE clauses. Find employees who joined in January:
SELECT
first_name,
last_name,
joining_date
FROM employees
WHERE EXTRACT(MONTH FROM joining_date) = 1;Find all activities from 2023:
SELECT *
FROM events
WHERE EXTRACT(YEAR FROM event_date) = 2023;Filtering by Month and Department
Use EXTRACT(MONTH) to filter for April (month 4) and LOWER() for case-insensitive department matching.
| worker_id | first_name | last_name | salary | joining_date | department |
|---|---|---|---|---|---|
| 1 | Monika | Arora | 100000 | 2014-02-20 | HR |
| 2 | Niharika | Verma | 80000 | 2014-06-11 | Admin |
| 3 | Vishal | Singhal | 300000 | 2014-02-20 | HR |
| 4 | Amitah | Singh | 500000 | 2014-02-20 | Admin |
| 5 | Vivek | Bhati | 500000 | 2014-06-11 | Admin |
Find the number of employees working in the Admin department that joined in April or later, in any year.
Combining with Other Functions
EXTRACT works seamlessly with aggregations. Count events by month:
The query groups hires by year. Change it to group by month instead, and add `ORDER BY` to sort by month.
Monthly Active Users
Use EXTRACT for the month and COUNT(DISTINCT) for unique users.
| id | time_id | user_id | customer_id | client_id | event_type | event_id |
|---|---|---|---|---|---|---|
| 1 | 2020-02-28 | 3668-QPYBK | Sendit | desktop | message sent | 3 |
| 2 | 2020-02-28 | 7892-POOKP | Connectix | mobile | file received | 2 |
| 3 | 2020-04-03 | 9763-GRSKD | Zoomit | desktop | video call received | 7 |
| 4 | 2020-04-02 | 9763-GRSKD | Connectix | desktop | video call received | 7 |
| 5 | 2020-02-06 | 9237-HQITU | Sendit | desktop | video call received | 7 |
Write a query that returns the number of unique users per client for each month. Assume all events occur within the same year, so only month needs to be be in the output as a number from 1 to 12.
Day of Week Analysis
Finding patterns by day of week is a product analytics staple. Are users more active on weekends? Do signups spike on Mondays?
-- PostgreSQL: 0 = Sunday, 6 = Saturday
SELECT
EXTRACT(DOW FROM created_at) AS day_of_week,
COUNT(*) AS signups
FROM users
GROUP BY EXTRACT(DOW FROM created_at)
ORDER BY day_of_week;Day of week numbering varies wildly! PostgreSQL: 0–6 (Sunday=0). MySQL DAYOFWEEK(): 1–7 (Sunday=1). MSSQL depends on server settings. Always test with a known date like ‘2024-01-01’ (a Monday) to verify your database’s behavior.
The query groups hires by month. Change it to group by day of week instead. Keep the descending sort by hire count.
Grouping by Year
Annual analysis is straightforward with EXTRACT:
SELECT
EXTRACT(YEAR FROM order_date) AS year,
SUM(total_order_cost) AS annual_revenue
FROM orders
GROUP BY EXTRACT(YEAR FROM order_date)
ORDER BY year;Violations by Year
Use EXTRACT(YEAR) with GROUP BY to count violations per year.
| business_id | business_name | business_address | business_city | business_state | business_postal_code | business_latitude | business_longitude | business_location | business_phone_number | inspection_id | inspection_date | inspection_score | inspection_type | violation_id | violation_description | risk_category |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5800 | John Chin Elementary School | 350 Broadway St | San Francisco | CA | 94133 | 37.8 | -122.4 | {'longitude': '-122.403154', 'needs_recoding': False, 'latitude': '37.798358', 'human_address': '{"address":"","city":"","state":"","zip":""}'} | 5800_20171017 | 2017-10-17 | 98 | Routine - Unscheduled | 5800_20171017_103149 | Wiping cloths not clean or properly stored or inadequate sanitizer | Low Risk | |
| 64236 | Sutter Pub and Restaurant | 700 Sutter St | San Francisco | CA | 94102 | 37.79 | -122.41 | {'longitude': '-122.41188', 'needs_recoding': False, 'latitude': '37.78881', 'human_address': '{"address":"","city":"","state":"","zip":""}'} | 64236_20170725 | 2017-07-25 | 88 | Routine - Unscheduled | 64236_20170725_103133 | Foods not protected from contamination | Moderate Risk | |
| 1991 | SRI THAI CUISINE | 4621 LINCOLN Way | San Francisco | CA | 94122 | 37.76 | -122.51 | {'longitude': '-122.507779', 'needs_recoding': False, 'latitude': '37.764073', 'human_address': '{"address":"","city":"","state":"","zip":""}'} | 1991_20171129 | 2017-11-29 | 86 | Routine - Unscheduled | 1991_20171129_103139 | Improper food storage | Low Risk | |
| 3816 | Washington Bakery & Restaurant | 733 Washington St | San Francisco | CA | 94108 | 37.8 | -122.41 | {'longitude': '-122.405845', 'needs_recoding': False, 'latitude': '37.795174', 'human_address': '{"address":"","city":"","state":"","zip":""}'} | 3816_20160728 | 2016-07-28 | 67 | Routine - Unscheduled | 3816_20160728_103108 | Contaminated or adulterated food | High Risk | |
| 39119 | Brothers Restaurant | 4128 GEARY Blvd | San Francisco | CA | 94118 | 37.78 | -122.46 | {'longitude': '-122.463762', 'needs_recoding': False, 'latitude': '37.781148', 'human_address': '{"address":"","city":"","state":"","zip":""}'} | 39119_20160718 | 2016-07-18 | 79 | Routine - Unscheduled | 39119_20160718_103133 | Foods not protected from contamination | Moderate Risk |
You are given a dataset of health inspections that includes details about violations. Each row represents an inspection, and if an inspection resulted in a violation, the `violation_id` column will contain a value. Count the total number of violations that occurred at **'Roxanne Cafe'** for each year, based on the inspection date. Output the year and the corresponding number of violations in ascending order of the year.
Key Takeaways
EXTRACT(part FROM date)pulls out year, month, day, hour, etc.- Dialect syntax varies significantly — know your target database
- PostgreSQL —
EXTRACT(); MySQL/MSSQL useYEAR(),MONTH(),DAY() - Day of week numbering differs by database — always verify with test data
EXTRACTworks inSELECT,WHERE,GROUP BY, andORDER BY
What’s Next
Extracting parts is just the beginning. Next, you’ll learn to perform date arithmetic: adding days, calculating differences between dates, and truncating to periods like weeks or months for time-series analysis.