Data types and dates
Every column has a type that decides what it can hold and how it sorts and compares. The set is small, but dates and times are where real bugs live, so they get the bulk of this lesson.
The core types
Across SQL databases you will meet a handful of families:
- Integers - whole numbers (
INTEGER,BIGINT). - Decimals / reals - fractions. Use exact
DECIMAL/NUMERICfor money (never floating-pointREAL, which rounds:0.1 + 0.2is not0.3). - Text - strings (
TEXT,VARCHAR(n)). - Boolean - true/false.
- NULL - "unknown / missing," its own thing (see the NULL section).
typeof() shows SQLite's view of a value:
SQLite uses type affinity: a column has a preferred type but will store others, so it is forgiving (and occasionally surprising). PostgreSQL and most engines are strictly typed - put text in an integer column and it errors. Newer SQLite also offers STRICT tables. Lean on real types; do not rely on a column quietly accepting the wrong one.
Casting
Convert a value's type with CAST(value AS type):
String functions
Text columns come with a toolkit for reshaping and searching strings - the everyday work of cleaning data and building labels. The core set is small and portable:
UPPER(s)/LOWER(s)- change case.LENGTH(s)- number of characters.SUBSTR(s, start, len)- a slice; positions are 1-based.s1 || s2- concatenate (glue strings together).TRIM(s)- strip leading and trailing spaces (LTRIM/RTRIMdo one side).REPLACE(s, find, with)- swap every occurrence of one substring for another.
LIKE searches by pattern (you met it in WHERE): % matches any run of characters, _ matches exactly one. Use it to find rows by a fragment of text:
'shoe' LIKE 'SHOE' is true in SQLite by default. Other engines differ - PostgreSQL's LIKE is case-sensitive and offers ILIKE for the insensitive version. Normalise with LOWER() when you need predictable behaviour across engines.
Exercise: clean and label
Finish it. For each product, show its name in lower case, its name length, and a code built as the upper-cased first three letters. Fill the blanks, then run - the box checks your result.
Show answer
SELECT LOWER(name) AS lower_name,
LENGTH(name) AS len,
UPPER(SUBSTR(name, 1, 3)) AS code
FROM products;
SUBSTR(name, 1, 3) takes three characters from position 1; UPPER then capitalises them.
Dates and times
There is no single "date" answer: PostgreSQL has rich date, timestamp, and timestamptz types; SQLite has no dedicated date type and stores dates as ISO-8601 text ('2026-02-20') or numbers, with functions to work on them. Storing as ISO text is the portable habit - it sorts correctly as text.
Key SQLite date functions (Postgres has equivalents like now(), EXTRACT, AGE):
date()/datetime() parse and shift dates, strftime() formats and extracts parts (great for "group by month"), and julianday() turns dates into numbers so you can subtract them.
The time-zone trap
The single most common date bug: mixing time zones. A bare timestamp like 2026-02-20 09:00 has no zone - is that London or Tokyo? Two rules save you:
- Store in UTC. Use a zone-aware type (
timestamptzin PostgreSQL) and keep everything in UTC internally. - Convert only at the edges - to the user's local zone when displaying, back to UTC when accepting input.
A timestamp without a zone (timestamp without time zone, or a naive string) is a latent bug the moment two regions touch your data.
Quick quiz
Types and dates
4 questions1Which type should store a money amount?
2How does SQLite store dates, lacking a dedicated date type?
3What is the golden rule for time zones?
4What does CAST('42' AS INTEGER) + 1 produce?
Changing data - DML and TCL - the write statements and the transactions that control them.