Skip to content
OrionStats
Module 2 · Functions · Date, string and numeric functions

Date, string and numeric functions

Bucket dates into months and weeks, compute durations, clean text and round numbers — with notes for PostgreSQL and SQL Server.

Intermediate45 min
Recommended first

This lesson builds on: Subqueries, CTEs and UNION.

Learning objectives
  • Extract date parts, truncate to month and compute day differences
  • Clean and combine text with string functions
  • Round, cast and safely divide numbers
  • Translate common functions between SQLite, PostgreSQL and SQL Server

Date functions

Dates drive almost every business query: monthly trends, year-over-year, days to convert. Each database names date functions differently — the concepts are identical.

Task SQLite (practice DB) PostgreSQL SQL Server
Year strftime('%Y', d) EXTRACT(YEAR FROM d) YEAR(d)
Month bucket strftime('%Y-%m', d) DATE_TRUNC('month', d) DATETRUNC(month, d) (2022+)
Add one month date(d, '+1 month') d + INTERVAL '1 month' DATEADD(month, 1, d)
Days between julianday(b) - julianday(a) b - a (dates) DATEDIFF(day, a, b)
Weekday strftime('%w', d) (0 = Sunday) EXTRACT(DOW FROM d) DATEPART(weekday, d)

Monthly completed orders in 2025:

SELECT strftime('%Y-%m', order_date) AS month,
       COUNT(*) AS completed_orders
FROM orders
WHERE status = 'completed' AND order_date >= '2025-01-01'
GROUP BY month
ORDER BY month;
Result · 12 rows
monthcompleted_orders
2025-01868
2025-02682
2025-031120
2025-04938
2025-051109
2025-061077
2025-071140
2025-081016
2025-09939
2025-10959
2025-111373
2025-121492

The December peak and February dip are seasonality — something to remember when comparing months.

Days from signup to first order (customers who ordered at least once):

WITH first_order AS (
  SELECT customer_id, MIN(order_date) AS first_order_date
  FROM orders GROUP BY customer_id
)
SELECT ROUND(AVG(julianday(f.first_order_date) - julianday(c.signup_date)), 1) AS avg_days_to_first_order
FROM customers c JOIN first_order f ON f.customer_id = c.customer_id
WHERE f.first_order_date >= c.signup_date;
Result · 1 row
avg_days_to_first_order
287.2
Comparing a timestamp with a date

In databases with real timestamps, WHERE created_at = '2025-03-01' matches only midnight exactly. Use ranges (>= '2025-03-01' AND < '2025-03-02').

String functions

Task SQLite / PostgreSQL SQL Server
Upper / lower UPPER(s), LOWER(s) same
Trim spaces TRIM(s) TRIM(s) (2017+)
Length LENGTH(s) LEN(s)
Substring SUBSTR(s, start, len) SUBSTRING(s, start, len)
Concatenate `a
Replace REPLACE(s, 'x', 'y') same
Find position INSTR(s, 'x') / POSITION('x' IN s) CHARINDEX('x', s)

Cleaning the messy city column in customers_raw:

SELECT city                    AS raw_city,
       UPPER(SUBSTR(TRIM(city), 1, 1)) || LOWER(SUBSTR(TRIM(city), 2)) AS clean_city,
       COUNT(*) AS rows
FROM customers_raw
WHERE TRIM(LOWER(city)) = 'baku'
GROUP BY city;
Result · 4 rows
raw_cityclean_cityrows
Baku Baku17
BAKUBaku13
BakuBaku16
bakuBaku22

Four spellings of one city collapse into one after TRIM and case normalisation. Extracting the email domain:

SELECT SUBSTR(email, INSTR(email, '@') + 1) AS domain, COUNT(*) AS customers
FROM customers GROUP BY domain;
Result · 1 row
domaincustomers
example.com4000

Numeric functions

  • ROUND(x, n) rounds to n decimals (round only for display, at the end).
  • CAST(x AS REAL) / CAST(x AS NUMERIC(10,2)) changes type.
  • ABS(x), CEIL/FLOOR (PostgreSQL, SQL Server; SQLite 3.35+ via math functions).
  • Safe division: x / NULLIF(y, 0) returns NULL instead of a division-by-zero error.
SELECT store_id,
       ROUND(100.0 * SUM(CASE WHEN status = 'returned' THEN 1 ELSE 0 END)
             / NULLIF(COUNT(*), 0), 2) AS return_rate_pct
FROM orders
GROUP BY store_id
ORDER BY return_rate_pct DESC;
Result · 9 rows
store_idreturn_rate_pct
66.43
26.34
86.23
56.04
16.01
35.74
45.59
75.48
94.47
Rounding before aggregating

SUM(ROUND(x, 0)) accumulates rounding error across thousands of rows. Aggregate exact values, round the final result.

Practice

Return each month of 2024 (YYYY-MM) and the number of orders placed (any status), ordered by month.

Log in to answer and get feedback

In customers_raw, how many distinct cities are there after trimming spaces and ignoring case? One number.

Log in to answer and get feedback

What does x / NULLIF(y, 0) return when y is 0?

Log in to answer and get feedback

Summary

  • Date bucketing (strftime, DATE_TRUNC) powers trends; durations come from date differences; use ranges, not equality, for timestamps.
  • String functions clean and standardise text before grouping.
  • Round at the end; use NULLIF to avoid division by zero; cast when types are wrong.