Phase 1 · Reading data: SELECT, WHERE and sorting · Topic 1.8

Built-in functions for text, numbers and dates

Should know

Functions such as UPPER, LENGTH, SUBSTR, ROUND and the date functions transform a value inside a query — one value in, one value out, for each row.

Not started

Step 1 Understand it

Why you need it. Data rarely arrives in the shape you need: names in mixed case, prices that need rounding, dates you want as "2024-03" or as "how many days ago". Functions do these small transformations inside the .

Watch it happen

AnimationRow functions: one value in, one value out, for every row
Read the animation as text
  1. A function takes a value and gives back one value — once for every row. Two rows in department 2: Dara and Eli.
  2. UPPER('Dara') = 'DARA'. Worked out for each row separately.
  3. LENGTH counts characters: 'Dara' has 4.
  4. SUBSTR(name, 1, 3): start at character 1 (SQL counts from 1), take 3: 'Dar'.
  5. Two rows in, two rows out. That is the difference from an aggregate like SUM, which turns many rows into one (Phase 2).

Picture it

flowchart LR
  A["one value per row<br/>Dara · 52000 · 2024-01-21"] --> F["a function<br/>UPPER · ROUND · strftime"] --> B["one new value per row<br/>DARA · 4333.33 · 2024-01"]
A changes each value on its own: one value in, one value out, for every . in Phase 2 do the opposite and squash many rows into one.

The idea

A function takes values in brackets and returns one value. Text: and LOWER change case, LENGTH counts characters, SUBSTR(text, start, length) cuts out a piece (counting from 1), TRIM removes spaces at the ends, and REPLACE swaps text. Numbers: ROUND(x, 2) rounds to 2 decimal places, ABS removes the minus sign. Dates are where differ most: uses and julianday; uses DATE_TRUNC, EXTRACT and date subtraction. These run once per row — unlike aggregate functions, which turn many rows into one (Phase 2).

The shape of it

sql
UPPER(text)   LENGTH(text)   SUBSTR(text, start, length)
ROUND(number, places)
strftime('%Y-%m', date)   -- SQLite: the year and month

Words in lower case are the parts you fill in.

How it works, step by step

  1. UPPER('Dara') is 'DARA'; LENGTH('Dara') is 4; SUBSTR('Dara', 1, 3) is 'Dar' (start at character 1, take 3).
  2. salary / 12.0 gives a long decimal; ROUND(…, 2) keeps 2 decimal places.
  3. strftime('%Y-%m', '2024-01-21') is '2024-01': the year and month, ready to group by.
  4. julianday turns a date into a day number, so subtracting two of them gives the days between.
  5. Functions can be nested: UPPER(SUBSTR(name, 1, 1)) is the first letter in capitals.

Analogy

Kitchen tools. A peeler (TRIM), a slicer (SUBSTR), a measuring spoon (). Each one takes one ingredient and gives back a changed version of it.

Where the analogy breaks

Kitchen tools are the same in every kitchen. Date functions are not the same in every database — so in an interview, say which database you are assuming, and the interviewer will usually accept any reasonable syntax.

Worked examples

Text functions

sql
SELECT name, UPPER(name) AS shout, LENGTH(name) AS letters, SUBSTR(name, 1, 3) AS first_three
FROM employees
WHERE dept_id = 2
ORDER BY name;

2 rows

nameshoutlettersfirst_three
DaraDARA4Dar
EliELI3Eli

Rounding

sql
SELECT name, salary, ROUND(salary / 12.0, 2) AS monthly
FROM employees
WHERE dept_id = 3 OR dept_id IS NULL
ORDER BY name;

2 rows

namesalarymonthly
Fatima520004333.33
Gus380003166.67

Dividing by 12.0, not 12, avoids . ROUND then keeps two decimal places.

Dates: the month, and days between

sql
SELECT order_id, order_date,
       strftime('%Y-%m', order_date) AS month,
       julianday('2024-03-31') - julianday(order_date) AS days_before_end_of_march
FROM orders
WHERE customer_id = 2
ORDER BY order_id;

2 rows

order_idorder_datemonthdays_before_end_of_march
1042024-01-212024-0170
1082024-03-012024-0330

This is SQLite's way. The notes below give the PostgreSQL, and versions.

Must-know points

  • A row function runs once per row and returns one value; an aggregate function squashes many rows into one.
  • Text positions start at 1, not 0: SUBSTR('Dara', 1, 3) is 'Dar'.
  • ROUND(x, 2) for display; divide by a decimal (12.0) to avoid integer division first.
  • Date functions differ the most between databases. Know your target database's month-truncation and date-difference functions.
  • Wrapping a in a function inside (WHERE UPPER(name) = …) stops a normal on that column being used.

Common mistakes

  • Counting text positions from 0, as in Python.
  • Rounding before summing, which adds up rounding errors. Sum first, round at the end.
  • Comparing dates stored as '06/01/2024' text — they compare as text, not as dates.

In other databases

  • Month of a date: PostgreSQL DATE_TRUNC('month', d) or TO_CHAR(d, 'YYYY-MM'); MySQL DATE_FORMAT(d, '%Y-%m'); SQL Server FORMAT(d, 'yyyy-MM'); SQLite strftime('%Y-%m', d).
  • Days between: PostgreSQL d2 - d1 (dates); MySQL DATEDIFF(d2, d1); SQL Server DATEDIFF(day, d1, d2); SQLite julianday(d2) - julianday(d1).
  • Year of a date: EXTRACT(YEAR FROM d) in PostgreSQL and MySQL; YEAR(d) in MySQL and SQL Server; strftime('%Y', d) in SQLite.
  • Text length is LENGTH in most databases but LEN in SQL Server.

Step 2 Go further (free, optional)

Optional. The notes above are written to be enough on their own. These free resources give a second explanation and more practice.

Step 3 Practise

Try each one yourself first: out loud, or written and run in the practice box below, which saves every query you run. Then open the answer and compare.

  1. ExplainSay what it does and when to use it, in plain words, with no notes.

    Explain the difference between a row function like UPPER and an aggregate like .

    Show answer

    A row function such as UPPER works on one value from one row and gives one value back. 8 employees in, 8 rows out, each with its own upper-case name.

    An aggregate such as SUM reads a whole column across many rows and gives back one value for the group. 8 salaries in, one total out. The rows collapse.

    That is why you can write SELECT name, UPPER(name), but not SELECT name, SUM(salary) without : one name per row can't sit next to one total for all rows.

  2. Writefrom a blank page, then run it

    Write a query that shows each customer's name and the part of their email before the @. (Hint: INSTR(email, '@') gives the position of the @.)

    Show answer

    INSTR(email, '@') finds where the @ is. Take the characters from position 1 up to one before it. For priya@mail.com the @ is at position 6, so take 5 characters.

    sql
    SELECT name, email, SUBSTR(email, 1, INSTR(email, '@') - 1) AS username
    FROM customers
    ORDER BY customer_id;

    6 rows

    nameemailusername
    Priyapriya@mail.compriya
    Tomtom@mail.comtom
    Lenalena@mail.comlena
    Omaromar@mail.comomar
    Zoezoe@mail.comzoe
    Tomtom@mail.comtom
  3. Predictsay it before you run it

    What does SELECT SUBSTR('Headphones', 5, 5), LENGTH(TRIM(' Pen ')), ROUND(2.675, 1); return?

    Show answer

    'phone', 3 and 2.7.

    • SUBSTR('Headphones', 5, 5): start at character 5 (counting from 1: H-e-a-d-p, so it starts at the p) and take 5 characters: "phone".
    • TRIM removes the spaces at both ends, leaving "Pen", which has 3 letters.
    • ROUND(2.675, 1) keeps one decimal place: 2.7.

    sql
    SELECT SUBSTR('Headphones', 5, 5), LENGTH(TRIM('  Pen  ')), ROUND(2.675, 1);

    1 row

    SUBSTR('Headphones', 5, 5)LENGTH(TRIM(' Pen '))ROUND(2.675, 1)
    phone32.7
  4. Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.

    How many days passed between each employee's hire_date and '2024-01-01'? What happens if one of the dates is ?

    Show answer

    Subtract the julianday numbers: each date becomes a count of days, so the difference is days between. Asha had been there 1,767 days by 1 January 2024.

    If either date is NULL, julianday gives NULL, and the subtraction gives NULL for that row. There is no error, just a blank. Decide what you want: drop those rows with WHERE hire_date IS NOT NULL, or show a default with . (In SQLite, SELECT julianday('2024-01-01') - julianday(NULL); returns NULL.)

    sql
    SELECT name, hire_date,
           julianday('2024-01-01') - julianday(hire_date) AS days_to_2024
    FROM employees
    ORDER BY emp_id;

    8 rows

    namehire_datedays_to_2024
    Asha2019-03-011767
    Ben2020-06-151295
    Chen2021-01-101086
    Dara2020-02-201411
    Eli2022-07-01549
    Fatima2021-09-12841
    Gus2023-04-03273
    Hana2022-11-30397

Practice box: write and run your answers here

Ctrl/⌘ + Enter runs it. Select part of the query to run only that part.

Step 4 Interview-ready, then prove it

Your interview answer

About 20 to 40 seconds, said out loud.

Scalar functions transform one value per row: functions like UPPER, SUBSTRING and TRIM, numeric ones like ROUND, and date functions for truncating to a month or finding the days between two dates. Date syntax is the least portable part of , so in an interview I name the dialect I am assuming — usually PostgreSQL, with DATE_TRUNC and date subtraction — and I avoid wrapping indexed columns in functions inside WHERE, because that blocks the index.

Follow-up questions

What is the difference between a scalar function and an aggregate function?

A scalar function returns one value per row (UPPER, ROUND). An aggregate returns one value per group of rows (SUM, ).

How do you group orders by month in PostgreSQL?

GROUP BY DATE_TRUNC('month', order_date), which turns every date into the first day of its month.

Tick each check when you can do it without notes

A topic is finished when you pass all four checks, not when you have read it. Be strict: if you had to look at the notes, it is not ticked yet.

  • Explain Say what it does and when to use it, in plain words, with no notes.
  • Write Write the query from a blank page, then run it to check.
  • Predict Read a query and say which rows it returns before you run it.
  • Edge cases Say what happens with NULLs, ties, duplicates or no rows at all, and fix it.

0 of 4 checks ticked.

Saved in this browser. Back it up or move it to another device.