- Process text with
SUBSTR,INSTR,REPLACE,UPPERandLENGTH - Split, shift and count days between dates with
date,strftimeandjulianday - Use integer division, remainders and rounding correctly and translate functions to other dialects
Data is rarely stored in exactly the form we need. Extracting the domain from an email, making a short name like “A. Mammadova”, calculating a payment deadline, adding VAT to a price and rounding it — all of this can be done in application code, but it is often simpler and faster to do it right in the query. In this lesson we cover three groups of functions. Be careful: functions are the part of SQL that differs most between dialects, so for each one we also give the MySQL and PostgreSQL equivalent.
String functions
LENGTH(s)— the number of characters;UPPER(s),LOWER(s)— upper and lower case.SUBSTR(s, start, length)— part of the text; positions start at 1, and without a length it goes to the end.INSTR(s, sub)— the first position of a substring (0 if absent);REPLACE(s, old, new)— replacement;TRIM(s)— removes spaces at both ends.
SELECT UPPER(last_name) AS upper_name,
LENGTH(last_name) AS len,
SUBSTR(first_name, 1, 1) || '. ' || last_name AS short_name,
SUBSTR(email, INSTR(email, '@') + 1) AS domain
FROM students
WHERE id IN (1, 3, 4)
ORDER BY id;▸ Expected output
upper_name | len | short_name | domain MəMMəDOVA | 9 | A. Məmmədova | example.com HüSEYNOVA | 9 | L. Hüseynova | example.com QULIYEV | 7 | E. Quliyev | NULL
NULL returns NULL again.Date functions
SQLite has no separate date type: dates are stored as text in the YYYY-MM-DD format. strftime(format, date) extracts parts of a date (%Y — year, %m — month, %w — day of the week, 0 = Sunday), and date(date, modifier...) shifts a date: '+30 days', '-1 month', 'start of month'.
SELECT id, order_date,
strftime('%Y', order_date) AS year,
strftime('%m', order_date) AS month,
strftime('%w', order_date) AS weekday,
date(order_date, '+30 days') AS pay_until
FROM orders
WHERE id <= 4
ORDER BY id;▸ Expected output
id | order_date | year | month | weekday | pay_until 1 | 2025-01-15 | 2025 | 01 | 3 | 2025-02-14 2 | 2025-01-15 | 2025 | 01 | 3 | 2025-02-14 3 | 2025-02-02 | 2025 | 02 | 0 | 2025-03-04 4 | 2025-02-10 | 2025 | 02 | 1 | 2025-03-12
To count the days between two dates, we turn them into numbers with julianday — the number of days since a fixed starting point — and subtract. Below we find how many days each customer waited between signing up and their first order.
SELECT c.name, c.joined_on,
MIN(o.order_date) AS first_order,
CAST(julianday(MIN(o.order_date)) - julianday(c.joined_on) AS INTEGER)
AS days_to_first_order
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id, c.name, c.joined_on
ORDER BY days_to_first_order, c.name;▸ Expected output
name | joined_on | first_order | days_to_first_order Zəhra Hüseynli | 2025-02-14 | 2025-04-09 | 54 Mehmet Kaya | 2025-04-01 | 2025-06-12 | 72 John Carter | 2024-08-30 | 2025-03-18 | 200 Olga Ivanova | 2024-06-18 | 2025-03-05 | 260 Emre Yılmaz | 2024-05-05 | 2025-02-10 | 281 Lalə Əhmədova | 2024-03-22 | 2025-02-02 | 317 Anar Mustafayev | 2024-01-10 | 2025-01-15 | 371
Math functions and rounding
Dividing two integers gives an integer again in SQLite, PostgreSQL and SQL Server: 7 / 2 = 3, and % gives the remainder. That is useful for problems like packing: 500 notebooks in boxes of 24 make 20 full boxes with 20 notebooks left over. When we add 18% VAT to a price, we round the result to hundredths of a manat with ROUND(x, 2).
SELECT name, stock,
stock / 24 AS full_boxes,
stock % 24 AS left_over,
ROUND(price * 1.18, 2) AS price_with_vat
FROM products
WHERE category IN ('Stationery', 'Accessories')
ORDER BY id;▸ Expected output
name | stock | full_boxes | left_over | price_with_vat Notebook | 500 | 20 | 20 | 3.78 Pen set | 300 | 12 | 12 | 9.32 Backpack | 60 | 2 | 12 | 64.9 Water bottle | 120 | 5 | 0 | 14.16
SELECT 7 / 2 AS int_div,
7 / 2.0 AS real_div,
ROUND(2.5) AS half_up,
ROUND(-2.5) AS half_down,
CAST(-12.9 AS INTEGER) AS truncated;▸ Expected output
int_div | real_div | half_up | half_down | truncated 3 | 3.5 | 3 | -3 | -12
ROUND rounds halves away from zero, while CAST(... AS INTEGER) simply drops the fractional part.Math functions such as SQRT, POWER, FLOOR, CEIL and LN are always available in PostgreSQL and MySQL. They were added to SQLite in version 3.35, but only in builds compiled with a special option, so portable queries for SQLite are safer if they stick to ROUND, ABS, CAST and %.
A “dictionary” between dialects
| Task | SQLite | MySQL | PostgreSQL |
|---|---|---|---|
| Number of characters | LENGTH(s) | CHAR_LENGTH(s) | LENGTH(s) |
| Part of a string | SUBSTR(s, 2, 3) | SUBSTRING(s, 2, 3) | SUBSTRING(s FROM 2 FOR 3) |
| Position of a substring | INSTR(s, '@') | LOCATE('@', s) | POSITION('@' IN s) |
| Concatenation | a || b | CONCAT(a, b) | a || b |
| Year of a date | strftime('%Y', d) | YEAR(d) | EXTRACT(YEAR FROM d) |
| Add 30 days | date(d, '+30 days') | DATE_ADD(d, INTERVAL 30 DAY) | d + INTERVAL '30 days' |
| Days between dates | julianday(b) - julianday(a) | DATEDIFF(b, a) | b - a |
| Start of the month | date(d, 'start of month') | DATE_FORMAT(d, '%Y-%m-01') | date_trunc('month', d) |
| Today's date | date('now') | CURDATE() | CURRENT_DATE |
LENGTH counts bytes, not characters: a two-byte letter such as ü gives 2. In PostgreSQL, b - a returns a number of days only for columns of type DATE.For every student who has an email, show the part of the email before the @ sign (login) and the first_name. Sort by login.
SELECT email, first_name
FROM students
WHERE email IS NOT NULL
ORDER BY email;▸ Expected output
login | first_name aysel | Aysel fidan | Fidan gunay | Günay kamran | Kamran leyla | Leyla murad | Murad nigar | Nigar orxan | Orxan rashad | Rəşad sabina | Səbinə
For each month (month, e.g. 2025-02), show the number of orders (orders) and the last day of that month (month_end). Sort by month.
SELECT SUBSTR(order_date, 1, 7) AS month,
COUNT(*) AS orders
-- add month_end
FROM orders
GROUP BY month
ORDER BY month;▸ Expected output
month | orders | month_end 2025-01 | 2 | 2025-01-31 2025-02 | 2 | 2025-02-28 2025-03 | 2 | 2025-03-31 2025-04 | 2 | 2025-04-30 2025-05 | 1 | 2025-05-31 2025-06 | 2 | 2025-06-30 2025-07 | 1 | 2025-07-31
Key points
SUBSTR,INSTR,REPLACE,TRIMand||cut, search and join text; in SQLite positions are counted from 1.- SQLite's
UPPER/LOWERchange only A–Z; in MySQL,LENGTHcounts bytes. strftimesplits a date into parts,date(d, '+30 days')shifts it, and ajuliandaydifference counts days.- Integer division gives an integer (
7 / 2 = 3),%gives the remainder, andROUND(x, 2)rounds to hundredths. - Functions differ the most between dialects — check an equivalents table when moving to another DBMS.
Check yourself
10 questions. Every correct answer earns XP.
SUBSTR('Bakı', 2, 2) return?