Skip to examples

A visual reference · SQLite

SQLite,
one change at a time

From a first SELECT to relational queries, transactions, indexes, JSON and full-text search. Complete source. A small change. Its actual result.

The Org file is the canonical source. Read the HTML guide beside your work, or download its PDF and source for offline reading.

Last changes: 2026-10-31. Introduced runnable SQLite examples of queries, joins, window functions, transactions, indexes, JSON and full-text search, with verified results and downloadable databases.

Download PDFRendered tour · read offline
96worked examples
16topic families
1canonical Org source

Browse by query and result

SQL and result gallery

96 independent examples. Choose a preview to read the complete query, its result and explanations. An ellipsis marks shortened code or output.

01 · A first SELECT

02 · Filtering, NULL and ordering

03 · Expressions, text and dates

04 · Aggregates and grouped questions

05 · Joins and missing relationships

06 · Subqueries, CTEs and sets

07 · Window functions

08 · Recursive queries

09 · Schema, types and constraints

10 · Writing data, views and triggers

11 · Transactions and savepoints

12 · Indexes and query plans

13 · JSON documents

14 · Full-text search

15 · Inspection and database copies

16 · Practical query patterns

Reading the tour

Three ways to see a change

SQL program

Read the query beside its real result; expand the complete schema and fixture.

Actual rows

Inspect typed SQLite output, NULL values, query plans and expected constraint failures.

One change

Follow a comparison, run the standalone SQL or explore its downloadable database.

Read from the beginning or jump to a stable example ID. Each example includes its complete input and captured result. Copy link preserves the address of the guide you are reading, including its current domain and path.

The starting atlas supplies twenty-four relational questions. This guide retains all twenty-four topics, develops them into complete independent programs, and adds schema design, data changes, transactions, indexes, JSON, full-text search and practical report patterns.

Every example starts with a new database. The concise view shows the statements to study; expand the complete program for its schema and fixture. No example depends on having run an earlier one. Run a downloaded SQL file in a fresh working directory with sqlite3 -header -box :memory: < SQL-01-001.sql. The WAL example needs a fresh file database instead of :memory:. The VACUUM INTO example writes snapshot.sqlite3 in its working directory.

Results are actual output from the native sqlite3 CLI. The guide renders its JSON rows as tables. NULL means an absent SQL value; italic empty text is an empty string. A failed constraint is labelled as an expected diagnostic. Only nonempty result sets produce JSON arrays in this CLI format. Downloads include the standalone SQL, captured results and a data snapshot after the example. Downloads use rollback-journal mode to remain self-contained; the WAL example still executes in WAL mode and reports that native result. If a failed program leaves a transaction open, connection close rolls it back before the database is saved.

Starting database

The shared fixture has eight synthetic observations. Values total 100. Site D has no lookup label, the seventh calendar day has no observation, and notes contain NULL. The site column is deliberately not a declared foreign key, so lookup gaps remain available for join examples. Dedicated constraint examples create actual foreign-key relationships with enforcement enabled.

id site value day note
1 A 12 2026-01-01 NULL
2 B 20 2026-01-02 checked
3 A 18 2026-01-03 NULL
4 B 8 2026-01-04 recheck
5 C 10 2026-01-05 NULL
6 A 6 2026-01-06 checked
7 C 10 2026-01-08 archived
8 D 16 2026-01-09 NULL

Lookup labels: A = River; B = Hill; C = Coast. Full-text examples use a separate four-document synthetic corpus in their complete source. All dates and input values are fixed. Query-plan wording depends on the SQLite version; these examples show actual chosen plans rather than timing claims. Full preparation requires SQLite 3.45 or newer with JSON and FTS5.

References and provenance

The atlas supplies the starting questions. This guide uses a new, explicitly listed fixture and independent explanations, comparisons and added examples. All twenty-four original topics are mapped in ATLAS_PANEL properties and sources/atlas-provenance.json. No poster builder or external dataset is needed. SQLite and its installed extensions remain external dependencies.

01 · A first SELECT

SQL-01-001

Select stored columns

A projection chooses which fields answer the question. An explicit ORDER BY defines the returned row order.

Back to result gallery

SQL to study

SELECT id,site,value FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idsitevalue
1A12.0
2B20.0
3A18.0
4B8.0
5C10.0
6A6.0
7C10.0
8D16.0

SQLite · exit 0

Clause or featureMeaning
SELECTChoose the output columns.
FROM sampleRead the shared observation table.
ORDER BY idReturn a deterministic order.
Complete standalone SQL and starting fixture
-- SQL-01-001: Select stored columns
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,site,value FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-01-002

Filter individual observations

WHERE tests individual rows before grouping or window calculations. Here the cutoff includes values equal to sixteen.

Back to result gallery

Compare with SQL-01-001 · Select stored columns.

SQL to study

SELECT id,site,value
FROM sample WHERE value>=16 ORDER BY id;

Actual SQLite result

Result 1 · 3 rows

idsitevalue
2B20.0
3A18.0
8D16.0

SQLite · exit 0

Clause or featureMeaning
WHERERetain only rows whose condition is true.
>=16Include the threshold itself.
Complete standalone SQL and starting fixture
-- SQL-01-002: Filter individual observations
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,site,value
FROM sample WHERE value>=16 ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-01-003

Find the top three

Sort the largest values first, then use the primary key as a tie-breaker. LIMIT selects a deterministic top set.

Back to result gallery

SQL to study

SELECT id,value FROM sample
ORDER BY value DESC,id LIMIT 3;

Actual SQLite result

Result 1 · 3 rows

idvalue
220.0
318.0
816.0

SQLite · exit 0

Clause or featureMeaning
DESCSort values from largest to smallest.
idBreak any ties consistently.
LIMIT 3Return at most three rows.
Complete standalone SQL and starting fixture
-- SQL-01-003: Find the top three
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,value FROM sample
ORDER BY value DESC,id LIMIT 3;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-01-004

Return distinct values

DISTINCT removes duplicate site values. Ordering is a separate operation and remains explicit.

Back to result gallery

SQL to study

SELECT DISTINCT site FROM sample ORDER BY site;

Actual SQLite result

Result 1 · 4 rows

site
A
B
C
D

SQLite · exit 0

Clause or featureMeaning
DISTINCTReturn each selected value once.
ORDER BY siteSort the unique results.
Complete standalone SQL and starting fixture
-- SQL-01-004: Return distinct values
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT DISTINCT site FROM sample ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-01-005

Calculate a derived column

Expressions can calculate new columns without changing stored observations. AS supplies a useful result name.

Back to result gallery

SQL to study

SELECT id,value*2 AS doubled
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

iddoubled
124.0
240.0
336.0
416.0
520.0
612.0
720.0
832.0

SQLite · exit 0

Clause or featureMeaning
value*2Calculate from each stored value.
AS doubledName the result column.
Complete standalone SQL and starting fixture
-- SQL-01-005: Calculate a derived column
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,value*2 AS doubled
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-01-006

Classify with CASE

CASE turns an explicit rule into a derived label. Every row receives either high or low.

Back to result gallery

SQL to study

SELECT id,CASE WHEN value>=16 THEN 'high'
               ELSE 'low' END AS class
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idclass
1low
2high
3high
4low
5low
6low
7low
8high

SQLite · exit 0

Clause or featureMeaning
CASE WHENEvaluate a classification condition.
ELSEProvide the alternative class.
Complete standalone SQL and starting fixture
-- SQL-01-006: Classify with CASE
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,CASE WHEN value>=16 THEN 'high'
               ELSE 'low' END AS class
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

02 · Filtering, NULL and ordering

SQL-02-001

Display a value for NULL

COALESCE substitutes a display value for an unknown note. The stored NULL remains unchanged.

Back to result gallery

SQL to study

SELECT id,coalesce(note,'(missing)') AS note
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idnote
1(missing)
2checked
3(missing)
4recheck
5(missing)
6checked
7archived
8(missing)

SQLite · exit 0

Clause or featureMeaning
coalesceReturn the first non-NULL argument.
NULLRepresent an absent value, not an empty string.
Complete standalone SQL and starting fixture
-- SQL-02-001: Display a value for NULL
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,coalesce(note,'(missing)') AS note
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-02-002

Test NULL explicitly

Equality with NULL produces unknown. IS NULL is the predicate for absent values.

Back to result gallery

Compare with SQL-02-001 · Display a value for NULL.

SQL to study

SELECT id,note FROM sample
WHERE note IS NULL ORDER BY id;

Actual SQLite result

Result 1 · 4 rows

idnote
1NULL
3NULL
5NULL
8NULL

SQLite · exit 0

Clause or featureMeaning
IS NULLTest whether a value is absent.
WHEREUnknown conditions do not pass the filter.
Complete standalone SQL and starting fixture
-- SQL-02-002: Test NULL explicitly
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,note FROM sample
WHERE note IS NULL ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-02-003

Combine range and membership filters

BETWEEN includes both endpoints. IN expresses a finite membership test without repeating equality comparisons.

Back to result gallery

SQL to study

SELECT id,site,value FROM sample
WHERE value BETWEEN 10 AND 18
  AND site IN ('A','C') ORDER BY id;

Actual SQLite result

Result 1 · 4 rows

idsitevalue
1A12.0
3A18.0
5C10.0
7C10.0

SQLite · exit 0

Clause or featureMeaning
BETWEENUse an inclusive numeric interval.
INAccept either named site.
ANDRequire both conditions.
Complete standalone SQL and starting fixture
-- SQL-02-003: Combine range and membership filters
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,site,value FROM sample
WHERE value BETWEEN 10 AND 18
  AND site IN ('A','C') ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-02-004

Match and normalize text

LIKE supports percent as a variable-length wildcard. This example also exposes lowercasing as a distinct calculation.

Back to result gallery

SQL to study

SELECT id,lower(note) AS note FROM sample
WHERE note LIKE 'check%' ORDER BY id;

Actual SQLite result

Result 1 · 2 rows

idnote
2checked
6checked

SQLite · exit 0

Clause or featureMeaning
LIKE check%Match the chosen prefix.
lowerNormalize the displayed text; it does not update storage.
Complete standalone SQL and starting fixture
-- SQL-02-004: Match and normalize text
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,lower(note) AS note FROM sample
WHERE note LIKE 'check%' ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-02-005

Place NULL values last

NULLS LAST makes the missing-value placement explicit. A primary-key tie-breaker fixes the order among equal notes.

Back to result gallery

SQL to study

SELECT id,note FROM sample
ORDER BY note NULLS LAST,id;

Actual SQLite result

Result 1 · 8 rows

idnote
7archived
2checked
6checked
4recheck
1NULL
3NULL
5NULL
8NULL

SQLite · exit 0

Clause or featureMeaning
NULLS LASTPlace absent notes after actual strings.
idStabilize ties within each note value.
Complete standalone SQL and starting fixture
-- SQL-02-005: Place NULL values last
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,note FROM sample
ORDER BY note NULLS LAST,id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-02-006

Continue with keyset pagination

A known key gives the starting point for the next page. This example returns the next three primary keys after four.

Back to result gallery

SQL to study

SELECT id,site FROM sample
WHERE id>4 ORDER BY id LIMIT 3;

Actual SQLite result

Result 1 · 3 rows

idsite
5C
6A
7C

SQLite · exit 0

Clause or featureMeaning
id>4Continue after the last seen key.
ORDER BY idUse the same key for filtering and ordering.
LIMITBound the page size.
Complete standalone SQL and starting fixture
-- SQL-02-006: Continue with keyset pagination
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,site FROM sample
WHERE id>4 ORDER BY id LIMIT 3;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

03 · Expressions, text and dates

SQL-03-001

Distinguish integer and real arithmetic

Operand types affect division. Cast deliberately when a fractional result is required.

Back to result gallery

SQL to study

SELECT 5/2 AS integer_division,
       5.0/2 AS real_division,
       CAST(5 AS REAL)/2 AS cast_division;

Actual SQLite result

Result 1 · 1 rows

integer_divisionreal_divisioncast_division
22.52.5

SQLite · exit 0

Clause or featureMeaning
Integer divisionTwo integer operands produce an integer quotient.
CAST AS REALChoose real arithmetic explicitly.
Complete standalone SQL and starting fixture
-- SQL-03-001: Distinguish integer and real arithmetic
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT 5/2 AS integer_division,
       5.0/2 AS real_division,
       CAST(5 AS REAL)/2 AS cast_division;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-03-002

Format a consistent identifier

printf applies a fixed-width numeric format; lower changes the site label independently.

Back to result gallery

SQL to study

SELECT printf('S%02d',id) AS label,lower(site) AS site
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

labelsite
S01a
S02b
S03a
S04b
S05c
S06a
S07c
S08d

SQLite · exit 0

Clause or featureMeaning
printf S%02dPad integer identifiers with a leading zero.
lowerConvert ASCII site letters to lowercase.
Complete standalone SQL and starting fixture
-- SQL-03-002: Format a consistent identifier
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT printf('S%02d',id) AS label,lower(site) AS site
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-03-003

Clean and join text

Text functions can clean an input and build a display label while preserving the original stored values.

Back to result gallery

SQL to study

SELECT trim('  River  ') AS cleaned,
       'site-'||lower('A') AS slug,
       replace('north_bank','_',' ') AS title;

Actual SQLite result

Result 1 · 1 rows

cleanedslugtitle
Riversite-anorth bank

SQLite · exit 0

Clause or featureMeaning
trimRemove surrounding spaces.
||Concatenate text values.
replaceSubstitute an exact substring.
Complete standalone SQL and starting fixture
-- SQL-03-003: Clean and join text
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT trim('  River  ') AS cleaned,
       'site-'||lower('A') AS slug,
       replace('north_bank','_',' ') AS title;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-03-004

Guard a denominator with NULLIF

NULLIF converts a zero denominator into NULL. COALESCE supplies a display fallback only after the calculation.

Back to result gallery

SQL to study

SELECT 12.0/nullif(0,0) AS unknown_ratio,
       coalesce(12.0/nullif(0,0),0) AS display_ratio,
       round(12.0/5,2) AS rounded;

Actual SQLite result

Result 1 · 1 rows

unknown_ratiodisplay_ratiorounded
NULL02.4

SQLite · exit 0

Clause or featureMeaning
nullif(a,b)Return NULL when the arguments are equal.
Display fallbackZero here is a presentation choice, not the unknown mathematical result.
Complete standalone SQL and starting fixture
-- SQL-03-004: Guard a denominator with NULLIF
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT 12.0/nullif(0,0) AS unknown_ratio,
       coalesce(12.0/nullif(0,0),0) AS display_ratio,
       round(12.0/5,2) AS rounded;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-03-005

Measure elapsed days

ISO date text and julianday provide a reproducible elapsed-day calculation. The reference date is fixed rather than taken from the clock.

Back to result gallery

SQL to study

SELECT id,CAST(julianday(day)-julianday('2026-01-01')
               AS INTEGER) AS days
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

iddays
10
21
32
43
54
65
77
88

SQLite · exit 0

Clause or featureMeaning
juliandayMap a date to a numeric day count.
Fixed originKeep this calculation reproducible.
Complete standalone SQL and starting fixture
-- SQL-03-005: Measure elapsed days
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,CAST(julianday(day)-julianday('2026-01-01')
               AS INTEGER) AS days
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-03-006

Calculate a calendar boundary

Date modifiers are applied from left to right. Compute the last day of the chosen month without relying on its length.

Back to result gallery

SQL to study

SELECT date('2026-02-12','start of month','+1 month','-1 day')
       AS month_end,
       strftime('%Y-%m','2026-02-12') AS month;

Actual SQLite result

Result 1 · 1 rows

month_endmonth
2026-02-282026-02

SQLite · exit 0

Clause or featureMeaning
start of monthMove to day one.
+1 month then -1 dayMove to the last day of the original month.
strftimeChoose a date display format.
Complete standalone SQL and starting fixture
-- SQL-03-006: Calculate a calendar boundary
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT date('2026-02-12','start of month','+1 month','-1 day')
       AS month_end,
       strftime('%Y-%m','2026-02-12') AS month;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

04 · Aggregates and grouped questions

SQL-04-001

Summarize the entire table

Aggregates collapse all observations into one summary row. COUNT(*) counts rows regardless of missing notes.

Back to result gallery

SQL to study

SELECT count(*) AS n,round(avg(value),2) AS mean,
       min(value) AS minimum,max(value) AS maximum
FROM sample;

Actual SQLite result

Result 1 · 1 rows

nmeanminimummaximum
812.56.020.0

SQLite · exit 0

Clause or featureMeaning
count(*)Count observation rows.
avg / min / maxCompute summary statistics over values.
Complete standalone SQL and starting fixture
-- SQL-04-001: Summarize the entire table
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT count(*) AS n,round(avg(value),2) AS mean,
       min(value) AS minimum,max(value) AS maximum
FROM sample;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-04-002

Group observations by site

GROUP BY changes the unit of the result from an observation to a site. Each output row is a group summary.

Back to result gallery

SQL to study

SELECT site,count(*) AS n,sum(value) AS total
FROM sample GROUP BY site ORDER BY site;

Actual SQLite result

Result 1 · 4 rows

sitentotal
A336.0
B228.0
C220.0
D116.0

SQLite · exit 0

Clause or featureMeaning
GROUP BY siteForm one group per site.
ORDER BYDefine the order of the grouped results.
Complete standalone SQL and starting fixture
-- SQL-04-002: Group observations by site
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT site,count(*) AS n,sum(value) AS total
FROM sample GROUP BY site ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-04-003

Filter groups with HAVING

WHERE filters input rows; HAVING filters completed groups. Here only group totals above twenty-five survive.

Back to result gallery

Compare with SQL-04-002 · Group observations by site.

SQL to study

SELECT site,sum(value) AS total FROM sample
GROUP BY site HAVING sum(value)>25 ORDER BY site;

Actual SQLite result

Result 1 · 2 rows

sitetotal
A36.0
B28.0

SQLite · exit 0

Clause or featureMeaning
HAVINGTest an aggregate after grouping.
sum(value)>25Keep only groups that meet the total threshold.
Complete standalone SQL and starting fixture
-- SQL-04-003: Filter groups with HAVING
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT site,sum(value) AS total FROM sample
GROUP BY site HAVING sum(value)>25 ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-04-004

Count missing and present values

COUNT(column) ignores NULL. FILTER restricts one aggregate without removing rows from the other aggregates.

Back to result gallery

SQL to study

SELECT site,count(*) AS n,count(note) AS noted,
       count(*) FILTER (WHERE note IS NULL) AS missing
FROM sample GROUP BY site ORDER BY site;

Actual SQLite result

Result 1 · 4 rows

sitennotedmissing
A312
B220
C211
D101

SQLite · exit 0

Clause or featureMeaning
count(note)Count only non-NULL notes.
FILTERApply a condition to one aggregate.
Complete standalone SQL and starting fixture
-- SQL-04-004: Count missing and present values
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT site,count(*) AS n,count(note) AS noted,
       count(*) FILTER (WHERE note IS NULL) AS missing
FROM sample GROUP BY site ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-04-005

Calculate a weighted mean

The weights are explicit inputs, not observation frequencies inferred from the row count.

Back to result gallery

SQL to study

WITH weights(id,w) AS (VALUES(1,1),(2,2),(3,1))
SELECT sum(s.value*w.w)/sum(w.w) AS weighted_mean
FROM sample AS s JOIN weights AS w ON w.id=s.id;

Actual SQLite result

Result 1 · 1 rows

weighted_mean
17.5

SQLite · exit 0

Clause or featureMeaning
Weighted numeratorMultiply every value by its weight.
Weight totalDivide by the sum of weights, not the number of rows.
Complete standalone SQL and starting fixture
-- SQL-04-005: Calculate a weighted mean
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH weights(id,w) AS (VALUES(1,1),(2,2),(3,1))
SELECT sum(s.value*w.w)/sum(w.w) AS weighted_mean
FROM sample AS s JOIN weights AS w ON w.id=s.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-04-006

Make an ordered list per group

Ordering inside the aggregate determines the list order. An outer ORDER BY independently orders the groups.

Back to result gallery

SQL to study

SELECT site,group_concat(id,',' ORDER BY id) AS ids
FROM sample GROUP BY site ORDER BY site;

Actual SQLite result

Result 1 · 4 rows

siteids
A1,3,6
B2,4
C5,7
D8

SQLite · exit 0

Clause or featureMeaning
group_concatCombine values with a chosen separator.
Aggregate ORDER BYSpecify list order explicitly; supported since SQLite 3.44.
Complete standalone SQL and starting fixture
-- SQL-04-006: Make an ordered list per group
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT site,group_concat(id,',' ORDER BY id) AS ids
FROM sample GROUP BY site ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

05 · Joins and missing relationships

SQL-05-001

Match a lookup with an inner join

Only observations with a matching lookup row survive. Site D deliberately has no lookup in this fixture.

Back to result gallery

SQL to study

SELECT s.id,t.label FROM sample AS s
JOIN site AS t ON t.id=s.site ORDER BY s.id;

Actual SQLite result

Result 1 · 7 rows

idlabel
1River
2Hill
3River
4Hill
5Coast
6River
7Coast

SQLite · exit 0

Clause or featureMeaning
JOIN ONRequire a matching site key.
AliasesDistinguish observation columns from lookup columns.
Complete standalone SQL and starting fixture
-- SQL-05-001: Match a lookup with an inner join
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT s.id,t.label FROM sample AS s
JOIN site AS t ON t.id=s.site ORDER BY s.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-05-002

Keep observations with missing metadata

A LEFT JOIN preserves every observation. The unmatched lookup fields become NULL.

Back to result gallery

Compare with SQL-05-001 · Match a lookup with an inner join.

SQL to study

SELECT s.id,t.label FROM sample AS s
LEFT JOIN site AS t ON t.id=s.site ORDER BY s.id;

Actual SQLite result

Result 1 · 8 rows

idlabel
1River
2Hill
3River
4Hill
5Coast
6River
7Coast
8NULL

SQLite · exit 0

Clause or featureMeaning
LEFT JOINPreserve the rows from the left side.
NULL labelExpose the missing lookup for D.
Complete standalone SQL and starting fixture
-- SQL-05-002: Keep observations with missing metadata
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT s.id,t.label FROM sample AS s
LEFT JOIN site AS t ON t.id=s.site ORDER BY s.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-05-003

Find missing relationships

NOT EXISTS expresses an anti-join. It reports observations whose site key has no lookup row.

Back to result gallery

SQL to study

SELECT s.id,s.site FROM sample AS s
WHERE NOT EXISTS(SELECT 1 FROM site AS t WHERE t.id=s.site)
ORDER BY s.id;

Actual SQLite result

Result 1 · 1 rows

idsite
8D

SQLite · exit 0

Clause or featureMeaning
NOT EXISTSKeep a row only when the correlated lookup is empty.
CorrelationMatch the lookup to the current observation.
Complete standalone SQL and starting fixture
-- SQL-05-003: Find missing relationships
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT s.id,s.site FROM sample AS s
WHERE NOT EXISTS(SELECT 1 FROM site AS t WHERE t.id=s.site)
ORDER BY s.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-05-004

Compare an ON filter with a WHERE filter

A right-side condition in ON controls matching. The same condition in WHERE removes the unmatched rows after the join.

Back to result gallery

Compare with SQL-05-002 · Keep observations with missing metadata.

SQL to study

SELECT 'ON' AS placement,count(*) AS n
FROM sample AS s LEFT JOIN site AS t
 ON t.id=s.site AND t.label='River'
UNION ALL
SELECT 'WHERE',count(*)
FROM sample AS s LEFT JOIN site AS t ON t.id=s.site
WHERE t.label='River'
ORDER BY placement;

Actual SQLite result

Result 1 · 2 rows

placementn
ON8
WHERE3

SQLite · exit 0

Clause or featureMeaning
ON conditionPreserve unmatched left rows.
WHERE conditionRequire a matching River label after the join.
Complete standalone SQL and starting fixture
-- SQL-05-004: Compare an ON filter with a WHERE filter
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT 'ON' AS placement,count(*) AS n
FROM sample AS s LEFT JOIN site AS t
 ON t.id=s.site AND t.label='River'
UNION ALL
SELECT 'WHERE',count(*)
FROM sample AS s LEFT JOIN site AS t ON t.id=s.site
WHERE t.label='River'
ORDER BY placement;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-05-005

See join multiplicity

A one-to-many relationship can return an observation more than once. This is expected join behaviour, not an accidental duplicate removal problem.

Back to result gallery

SQL to study

CREATE TABLE tag(sample_id INTEGER,tag TEXT);
INSERT INTO tag VALUES(1,'red'),(1,'checked'),(2,'blue');
SELECT s.id,t.tag FROM sample AS s
JOIN tag AS t ON t.sample_id=s.id ORDER BY s.id,t.tag;

Actual SQLite result

Result 1 · 3 rows

idtag
1checked
1red
2blue

SQLite · exit 0

Clause or featureMeaning
One-to-manyOne observation can have several tags.
Result unitThe output now represents an observation-tag pair.
Complete standalone SQL and starting fixture
-- SQL-05-005: See join multiplicity
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE tag(sample_id INTEGER,tag TEXT);
INSERT INTO tag VALUES(1,'red'),(1,'checked'),(2,'blue');
SELECT s.id,t.tag FROM sample AS s
JOIN tag AS t ON t.sample_id=s.id ORDER BY s.id,t.tag;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-05-006

Join a table to itself

Two aliases let one table play two roles. Date arithmetic finds observations exactly one day apart.

Back to result gallery

SQL to study

SELECT a.id AS first_id,b.id AS next_id
FROM sample AS a JOIN sample AS b
 ON b.day=date(a.day,'+1 day') ORDER BY a.id,b.id;

Actual SQLite result

Result 1 · 6 rows

first_idnext_id
12
23
34
45
56
78

SQLite · exit 0

Clause or featureMeaning
Two aliasesRefer to different rows of the same table.
Date relationRequire a one-day gap, not merely consecutive IDs.
Complete standalone SQL and starting fixture
-- SQL-05-006: Join a table to itself
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT a.id AS first_id,b.id AS next_id
FROM sample AS a JOIN sample AS b
 ON b.day=date(a.day,'+1 day') ORDER BY a.id,b.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

06 · Subqueries, CTEs and sets

SQL-06-001

Compare with a correlated group mean

The subquery depends on the outer row site. It compares each observation with its own group rather than the global mean.

Back to result gallery

SQL to study

SELECT s.id,s.site,s.value FROM sample AS s
WHERE s.value>(SELECT avg(t.value) FROM sample AS t
               WHERE t.site=s.site) ORDER BY s.id;

Actual SQLite result

Result 1 · 2 rows

idsitevalue
2B20.0
3A18.0

SQLite · exit 0

Clause or featureMeaning
Correlated subqueryRe-use the outer row site as a condition.
Group meanCompare within each site.
Complete standalone SQL and starting fixture
-- SQL-06-001: Compare with a correlated group mean
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT s.id,s.site,s.value FROM sample AS s
WHERE s.value>(SELECT avg(t.value) FROM sample AS t
               WHERE t.site=s.site) ORDER BY s.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-06-002

Name an intermediate result

A common table expression gives a query stage an inspectable name. The final query chooses its own output order.

Back to result gallery

SQL to study

WITH totals AS (
 SELECT site,sum(value) AS total FROM sample GROUP BY site
)
SELECT site,total FROM totals ORDER BY total DESC,site;

Actual SQLite result

Result 1 · 4 rows

sitetotal
A36.0
B28.0
C20.0
D16.0

SQLite · exit 0

Clause or featureMeaning
WITH totals ASName the grouped intermediate result.
Final SELECTSelect and order the intermediate rows.
Complete standalone SQL and starting fixture
-- SQL-06-002: Name an intermediate result
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH totals AS (
 SELECT site,sum(value) AS total FROM sample GROUP BY site
)
SELECT site,total FROM totals ORDER BY total DESC,site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-06-003

Use EXISTS as a membership question

EXISTS asks whether any qualifying observation is present. It does not multiply lookup rows by the number of matches.

Back to result gallery

SQL to study

SELECT t.id,t.label FROM site AS t
WHERE EXISTS(SELECT 1 FROM sample AS s
             WHERE s.site=t.id AND s.value>15) ORDER BY t.id;

Actual SQLite result

Result 1 · 2 rows

idlabel
ARiver
BHill

SQLite · exit 0

Clause or featureMeaning
EXISTSTest whether the subquery has a row.
One lookup rowReturn each qualifying site only once.
Complete standalone SQL and starting fixture
-- SQL-06-003: Use EXISTS as a membership question
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT t.id,t.label FROM site AS t
WHERE EXISTS(SELECT 1 FROM sample AS s
             WHERE s.site=t.id AND s.value>15) ORDER BY t.id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-06-004

Expose the NOT IN and NULL trap

A NULL in the right-hand set makes unmatched NOT IN comparisons unknown. NOT EXISTS tests actual equality matches instead.

Back to result gallery

Compare with SQL-05-003 · Find missing relationships.

SQL to study

SELECT 'NOT IN' AS method,count(*) AS n FROM sample
WHERE site NOT IN(SELECT id FROM site UNION ALL SELECT NULL)
UNION ALL
SELECT 'NOT EXISTS',count(*) FROM sample AS s
WHERE NOT EXISTS(SELECT 1 FROM site AS t WHERE t.id=s.site)
ORDER BY method;

Actual SQLite result

Result 1 · 2 rows

methodn
NOT EXISTS1
NOT IN0

SQLite · exit 0

Clause or featureMeaning
NULL in a setAn unmatched NOT IN test becomes unknown.
NOT EXISTSAvoid that NULL-dependent membership result.
Complete standalone SQL and starting fixture
-- SQL-06-004: Expose the NOT IN and NULL trap
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT 'NOT IN' AS method,count(*) AS n FROM sample
WHERE site NOT IN(SELECT id FROM site UNION ALL SELECT NULL)
UNION ALL
SELECT 'NOT EXISTS',count(*) FROM sample AS s
WHERE NOT EXISTS(SELECT 1 FROM site AS t WHERE t.id=s.site)
ORDER BY method;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-06-005

Compare UNION and UNION ALL

UNION removes duplicate result rows. UNION ALL retains them; both queries explicitly sort their output.

Back to result gallery

SQL to study

SELECT site FROM sample UNION SELECT id FROM site ORDER BY site;
SELECT site FROM sample UNION ALL SELECT id FROM site ORDER BY site;

Actual SQLite result

Result 1 · 4 rows

site
A
B
C
D

Result 2 · 11 rows

site
A
A
A
A
B
B
B
C
C
C
D

SQLite · exit 0

Clause or featureMeaning
UNIONApply set semantics to the selected rows.
UNION ALLPreserve repeated rows.
Complete standalone SQL and starting fixture
-- SQL-06-005: Compare UNION and UNION ALL
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT site FROM sample UNION SELECT id FROM site ORDER BY site;
SELECT site FROM sample UNION ALL SELECT id FROM site ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-06-006

Intersect and subtract sets

INTERSECT finds site keys shared by both tables. EXCEPT finds observed keys that are absent from the lookup.

Back to result gallery

SQL to study

SELECT site FROM sample INTERSECT SELECT id FROM site ORDER BY site;
SELECT site FROM sample EXCEPT SELECT id FROM site ORDER BY site;

Actual SQLite result

Result 1 · 3 rows

site
A
B
C

Result 2 · 1 rows

site
D

SQLite · exit 0

Clause or featureMeaning
INTERSECTKeep rows present on both sides.
EXCEPTKeep left rows absent on the right.
Complete standalone SQL and starting fixture
-- SQL-06-006: Intersect and subtract sets
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT site FROM sample INTERSECT SELECT id FROM site ORDER BY site;
SELECT site FROM sample EXCEPT SELECT id FROM site ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

07 · Window functions

SQL-07-001

Number rows within each site

ROW_NUMBER preserves observations while assigning positions within each site. A tie-breaker makes equal values deterministic.

Back to result gallery

SQL to study

SELECT id,site,row_number() OVER(
 PARTITION BY site ORDER BY value DESC,id) AS position
FROM sample ORDER BY site,position;

Actual SQLite result

Result 1 · 8 rows

idsiteposition
3A1
1A2
6A3
2B1
4B2
5C1
7C2
8D1

SQLite · exit 0

Clause or featureMeaning
PARTITION BYRestart numbering for each site.
Window ORDER BYChoose the order used for numbering.
Outer ORDER BYChoose the returned row order.
Complete standalone SQL and starting fixture
-- SQL-07-001: Number rows within each site
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,site,row_number() OVER(
 PARTITION BY site ORDER BY value DESC,id) AS position
FROM sample ORDER BY site,position;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-07-002

Calculate a running total

An explicit ROWS frame makes the accumulation boundary clear. Window aggregation retains one result per observation.

Back to result gallery

SQL to study

SELECT id,sum(value) OVER(ORDER BY id
 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idcumulative
112.0
232.0
350.0
458.0
568.0
674.0
784.0
8100.0

SQLite · exit 0

Clause or featureMeaning
ROWS frameUse physical rows in the window ordering.
UNBOUNDED PRECEDINGStart with the first row.
Complete standalone SQL and starting fixture
-- SQL-07-002: Calculate a running total
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,sum(value) OVER(ORDER BY id
 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-07-003

Measure change from the preceding row

LAG accesses a previous ordered row. The first difference is unknown, so its result is NULL.

Back to result gallery

SQL to study

SELECT id,value-lag(value) OVER(ORDER BY id) AS change
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idchange
1NULL
28.0
3-2.0
4-10.0
52.0
6-4.0
74.0
86.0

SQLite · exit 0

Clause or featureMeaning
lag(value)Read the previous row value.
First rowNo previous row exists.
Complete standalone SQL and starting fixture
-- SQL-07-003: Measure change from the preceding row
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,value-lag(value) OVER(ORDER BY id) AS change
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-07-004

Calculate a three-row moving mean

The first windows contain fewer than three rows. The unit is ordered observations rather than calendar days.

Back to result gallery

SQL to study

SELECT id,round(avg(value) OVER(ORDER BY id
 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) AS mean3
FROM sample ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idmean3
112.0
216.0
316.67
415.33
512.0
68.0
78.67
810.67

SQLite · exit 0

Clause or featureMeaning
2 PRECEDINGInclude up to two earlier observations.
ROWS not daysMissing calendar dates do not create rows.
Complete standalone SQL and starting fixture
-- SQL-07-004: Calculate a three-row moving mean
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,round(avg(value) OVER(ORDER BY id
 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) AS mean3
FROM sample ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-07-005

Compare rank and dense rank

Tied values share a rank. RANK leaves a gap after a tie; DENSE_RANK advances without a gap.

Back to result gallery

SQL to study

SELECT id,value,rank() OVER(ORDER BY value DESC) AS r,
 dense_rank() OVER(ORDER BY value DESC) AS dense_r
FROM sample ORDER BY value DESC,id;

Actual SQLite result

Result 1 · 8 rows

idvaluerdense_r
220.011
318.022
816.033
112.044
510.055
710.055
48.076
66.087

SQLite · exit 0

Clause or featureMeaning
TiesDo not add id to the rank window: doing so would break value ties.
Outer id orderUse id only to stabilize the displayed tied rows.
Complete standalone SQL and starting fixture
-- SQL-07-005: Compare rank and dense rank
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,value,rank() OVER(ORDER BY value DESC) AS r,
 dense_rank() OVER(ORDER BY value DESC) AS dense_r
FROM sample ORDER BY value DESC,id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-07-006

Give LAST_VALUE a complete frame

LAST_VALUE uses the end of its window frame. An explicit full-partition frame retrieves the final observation for every row.

Back to result gallery

SQL to study

SELECT id,first_value(value) OVER w AS first,
 last_value(value) OVER w AS last FROM sample
WINDOW w AS (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING
                         AND UNBOUNDED FOLLOWING) ORDER BY id;

Actual SQLite result

Result 1 · 8 rows

idfirstlast
112.016.0
212.016.0
312.016.0
412.016.0
512.016.0
612.016.0
712.016.0
812.016.0

SQLite · exit 0

Clause or featureMeaning
Named windowReuse the same definition for both functions.
UNBOUNDED FOLLOWINGExtend the frame to the final row.
Complete standalone SQL and starting fixture
-- SQL-07-006: Give LAST_VALUE a complete frame
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT id,first_value(value) OVER w AS first,
 last_value(value) OVER w AS last FROM sample
WINDOW w AS (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING
                         AND UNBOUNDED FOLLOWING) ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

08 · Recursive queries

SQL-08-001

Generate a finite sequence

A recursive CTE combines an anchor row with a terminating recursive step.

Back to result gallery

SQL to study

WITH RECURSIVE seq(n) AS (
 VALUES(1) UNION ALL SELECT n+1 FROM seq WHERE n<5
) SELECT n,n*n AS square FROM seq ORDER BY n;

Actual SQLite result

Result 1 · 5 rows

nsquare
11
24
39
416
525

SQLite · exit 0

Clause or featureMeaning
AnchorStart at one.
WHERE n<5Bound recursion explicitly.
UNION ALLKeep every generated step.
Complete standalone SQL and starting fixture
-- SQL-08-001: Generate a finite sequence
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH RECURSIVE seq(n) AS (
 VALUES(1) UNION ALL SELECT n+1 FROM seq WHERE n<5
) SELECT n,n*n AS square FROM seq ORDER BY n;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-08-002

Generate a calendar with missing days

A recursive date series supplies the missing seventh day. A LEFT JOIN then counts observations on every calendar day.

Back to result gallery

SQL to study

WITH RECURSIVE days(day) AS (
 VALUES('2026-01-01') UNION ALL
 SELECT date(day,'+1 day') FROM days WHERE day<'2026-01-09'
) SELECT d.day,count(s.id) AS n FROM days AS d
LEFT JOIN sample AS s ON s.day=d.day
GROUP BY d.day ORDER BY d.day;

Actual SQLite result

Result 1 · 9 rows

dayn
2026-01-011
2026-01-021
2026-01-031
2026-01-041
2026-01-051
2026-01-061
2026-01-070
2026-01-081
2026-01-091

SQLite · exit 0

Clause or featureMeaning
Date seriesCreate rows for dates without observations.
count(s.id)Count matches, not the preserved calendar row.
Complete standalone SQL and starting fixture
-- SQL-08-002: Generate a calendar with missing days
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH RECURSIVE days(day) AS (
 VALUES('2026-01-01') UNION ALL
 SELECT date(day,'+1 day') FROM days WHERE day<'2026-01-09'
) SELECT d.day,count(s.id) AS n FROM days AS d
LEFT JOIN sample AS s ON s.day=d.day
GROUP BY d.day ORDER BY d.day;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-08-003

Walk a parent-child hierarchy

Recursion follows child links from one root and carries the depth. The final path order gives a stable readable hierarchy.

Back to result gallery

SQL to study

CREATE TABLE node(id INTEGER PRIMARY KEY,parent INTEGER,name TEXT);
INSERT INTO node VALUES(1,NULL,'Archive'),(2,1,'Field'),
 (3,1,'Lab'),(4,2,'North'),(5,2,'South');
WITH RECURSIVE tree(id,path,depth) AS (
 SELECT id,name,0 FROM node WHERE parent IS NULL
 UNION ALL SELECT n.id,t.path||'/'||n.name,t.depth+1
 FROM node AS n JOIN tree AS t ON n.parent=t.id
) SELECT path,depth FROM tree ORDER BY path;

Actual SQLite result

Result 1 · 5 rows

pathdepth
Archive0
Archive/Field1
Archive/Field/North2
Archive/Field/South2
Archive/Lab1

SQLite · exit 0

Clause or featureMeaning
Carried columnsMaintain a path and depth through recursive steps.
Child joinExtend only from the current parent.
Complete standalone SQL and starting fixture
-- SQL-08-003: Walk a parent-child hierarchy
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE node(id INTEGER PRIMARY KEY,parent INTEGER,name TEXT);
INSERT INTO node VALUES(1,NULL,'Archive'),(2,1,'Field'),
 (3,1,'Lab'),(4,2,'North'),(5,2,'South');
WITH RECURSIVE tree(id,path,depth) AS (
 SELECT id,name,0 FROM node WHERE parent IS NULL
 UNION ALL SELECT n.id,t.path||'/'||n.name,t.depth+1
 FROM node AS n JOIN tree AS t ON n.parent=t.id
) SELECT path,depth FROM tree ORDER BY path;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-08-004

Prevent cycles in a graph walk

The visited path prevents a node from being repeated within one walk. Delimiters keep node one distinct from node eleven.

Back to result gallery

SQL to study

CREATE TABLE edge(a INTEGER,b INTEGER);
INSERT INTO edge VALUES(1,2),(2,3),(3,1),(2,4);
WITH RECURSIVE walk(id,path,depth) AS (
 VALUES(1,',1,',0) UNION ALL
 SELECT e.b,w.path||e.b||',',w.depth+1
 FROM edge AS e JOIN walk AS w ON e.a=w.id
 WHERE instr(w.path,','||e.b||',')=0 AND w.depth<8
) SELECT id,depth FROM walk ORDER BY depth,id;

Actual SQLite result

Result 1 · 4 rows

iddepth
10
21
32
42

SQLite · exit 0

Clause or featureMeaning
Visited pathReject a node already present in this path.
Depth boundAdd a separate finite upper bound.
Complete standalone SQL and starting fixture
-- SQL-08-004: Prevent cycles in a graph walk
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE edge(a INTEGER,b INTEGER);
INSERT INTO edge VALUES(1,2),(2,3),(3,1),(2,4);
WITH RECURSIVE walk(id,path,depth) AS (
 VALUES(1,',1,',0) UNION ALL
 SELECT e.b,w.path||e.b||',',w.depth+1
 FROM edge AS e JOIN walk AS w ON e.a=w.id
 WHERE instr(w.path,','||e.b||',')=0 AND w.depth<8
) SELECT id,depth FROM walk ORDER BY depth,id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-08-005

Carry an accumulated calculation

A recursive CTE can carry both a counter and a running product. This example calculates factorials through six.

Back to result gallery

SQL to study

WITH RECURSIVE fact(n,value) AS (
 VALUES(0,1) UNION ALL
 SELECT n+1,value*(n+1) FROM fact WHERE n<6
) SELECT n,value FROM fact ORDER BY n;

Actual SQLite result

Result 1 · 7 rows

nvalue
01
11
22
36
424
5120
6720

SQLite · exit 0

Clause or featureMeaning
State pairCarry the current n and factorial value.
TerminationStop before the integer product grows large.
Complete standalone SQL and starting fixture
-- SQL-08-005: Carry an accumulated calculation
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH RECURSIVE fact(n,value) AS (
 VALUES(0,1) UNION ALL
 SELECT n+1,value*(n+1) FROM fact WHERE n<6
) SELECT n,value FROM fact ORDER BY n;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-08-006

Expand a bill of materials

Quantities multiply along each relationship. Leaf totals combine all paths that reach the same final part.

Back to result gallery

SQL to study

CREATE TABLE component(parent TEXT,child TEXT,qty INTEGER);
INSERT INTO component VALUES('kit','case',1),('kit','pack',2),('pack','bolt',4);
WITH RECURSIVE bom(part,qty) AS (
 VALUES('kit',1) UNION ALL
 SELECT c.child,b.qty*c.qty FROM component AS c
 JOIN bom AS b ON c.parent=b.part
) SELECT part,sum(qty) AS needed FROM bom AS b
WHERE NOT EXISTS(SELECT 1 FROM component AS c WHERE c.parent=b.part)
GROUP BY part ORDER BY part;

Actual SQLite result

Result 1 · 2 rows

partneeded
bolt8
case1

SQLite · exit 0

Clause or featureMeaning
Multiply quantitiesTwo packs of four bolts require eight bolts.
Leaf filterKeep only parts with no children.
Complete standalone SQL and starting fixture
-- SQL-08-006: Expand a bill of materials
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE component(parent TEXT,child TEXT,qty INTEGER);
INSERT INTO component VALUES('kit','case',1),('kit','pack',2),('pack','bolt',4);
WITH RECURSIVE bom(part,qty) AS (
 VALUES('kit',1) UNION ALL
 SELECT c.child,b.qty*c.qty FROM component AS c
 JOIN bom AS b ON c.parent=b.part
) SELECT part,sum(qty) AS needed FROM bom AS b
WHERE NOT EXISTS(SELECT 1 FROM component AS c WHERE c.parent=b.part)
GROUP BY part ORDER BY part;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

09 · Schema, types and constraints

SQL-09-001

Reject a violated CHECK constraint

A CHECK constraint validates the stored row. This intentionally invalid insert produces a captured native diagnostic and an expected failure exit.

Back to result gallery

SQL to study

CREATE TABLE quantity(id INTEGER PRIMARY KEY,n INTEGER NOT NULL CHECK(n>=0));
INSERT INTO quantity VALUES(1,-1);

Actual SQLite result

Expected diagnostic

Error near line 21 of SQL-09-001.sql: CHECK constraint failed: n>=0

SQLite · expected exit 1

Clause or featureMeaning
NOT NULLRequire a known quantity.
CHECK(n>=0)Reject negative values.
Expected failureThe invalid row is not stored.
Complete standalone SQL and starting fixture
-- SQL-09-001: Reject a violated CHECK constraint
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE quantity(id INTEGER PRIMARY KEY,n INTEGER NOT NULL CHECK(n>=0));
INSERT INTO quantity VALUES(1,-1);
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-09-002

Enforce a foreign key

Foreign-key enforcement is enabled in every standalone fixture. This deliberately missing parent key is rejected.

Back to result gallery

SQL to study

CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(id INTEGER PRIMARY KEY,parent_id INTEGER REFERENCES parent(id));
INSERT INTO child VALUES(1,99);

Actual SQLite result

Expected diagnostic

Error near line 22 of SQL-09-002.sql: FOREIGN KEY constraint failed

SQLite · expected exit 1

Clause or featureMeaning
REFERENCESDeclare the relationship in the schema.
PRAGMA foreign_keys=ONEnable enforcement for this connection.
Complete standalone SQL and starting fixture
-- SQL-09-002: Enforce a foreign key
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(id INTEGER PRIMARY KEY,parent_id INTEGER REFERENCES parent(id));
INSERT INTO child VALUES(1,99);
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-09-003

Compare STRICT storage with ANY

STRICT tables check declared types. A numeric string can be losslessly converted to INTEGER, while ANY preserves the original text.

Back to result gallery

SQL to study

CREATE TABLE typed(n INTEGER,tag ANY) STRICT;
INSERT INTO typed VALUES('000123','000123');
SELECT n,typeof(n) AS n_type,tag,typeof(tag) AS tag_type FROM typed;

Actual SQLite result

Result 1 · 1 rows

nn_typetagtag_type
123integer000123text

SQLite · exit 0

Clause or featureMeaning
STRICTUse the table-level strict type rules.
ANYKeep the supplied storage type without numeric coercion.
Complete standalone SQL and starting fixture
-- SQL-09-003: Compare STRICT storage with ANY
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE typed(n INTEGER,tag ANY) STRICT;
INSERT INTO typed VALUES('000123','000123');
SELECT n,typeof(n) AS n_type,tag,typeof(tag) AS tag_type FROM typed;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-09-004

Store a generated column

The schema defines the total calculation once. Integer cents keep this example exact.

Back to result gallery

SQL to study

CREATE TABLE line(qty INTEGER,unit_cents INTEGER,
 total_cents INTEGER GENERATED ALWAYS AS (qty*unit_cents) STORED);
INSERT INTO line(qty,unit_cents) VALUES(3,120),(2,125);
SELECT qty,unit_cents,total_cents FROM line ORDER BY total_cents;

Actual SQLite result

Result 1 · 2 rows

qtyunit_centstotal_cents
2125250
3120360

SQLite · exit 0

Clause or featureMeaning
GENERATED ALWAYSCalculate the value from other columns.
STOREDPersist the generated result.
Integer centsAvoid a binary floating-point money example.
Complete standalone SQL and starting fixture
-- SQL-09-004: Store a generated column
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE line(qty INTEGER,unit_cents INTEGER,
 total_cents INTEGER GENERATED ALWAYS AS (qty*unit_cents) STORED);
INSERT INTO line(qty,unit_cents) VALUES(3,120),(2,125);
SELECT qty,unit_cents,total_cents FROM line ORDER BY total_cents;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-09-005

Use a composite key without rowid

WITHOUT ROWID uses the declared primary key as the table key. The two-part key identifies each coordinate entry.

Back to result gallery

SQL to study

CREATE TABLE coordinate(a TEXT,b INTEGER,value TEXT,
 PRIMARY KEY(a,b)) WITHOUT ROWID;
INSERT INTO coordinate VALUES('A',1,'north'),('A',2,'south'),('B',1,'east');
SELECT a,b,value FROM coordinate ORDER BY a,b;

Actual SQLite result

Result 1 · 3 rows

abvalue
A1north
A2south
B1east

SQLite · exit 0

Clause or featureMeaning
Composite primary keyIdentify a row with both a and b.
WITHOUT ROWIDUse that key as the underlying table key.
Complete standalone SQL and starting fixture
-- SQL-09-005: Use a composite key without rowid
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE coordinate(a TEXT,b INTEGER,value TEXT,
 PRIMARY KEY(a,b)) WITHOUT ROWID;
INSERT INTO coordinate VALUES('A',1,'north'),('A',2,'south'),('B',1,'east');
SELECT a,b,value FROM coordinate ORDER BY a,b;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-09-006

See how UNIQUE treats NULL

A UNIQUE constraint permits multiple NULL values. COUNT(column) and COUNT(DISTINCT column) ignore those unknown values.

Back to result gallery

SQL to study

CREATE TABLE code(id INTEGER PRIMARY KEY,label TEXT UNIQUE);
INSERT INTO code VALUES(1,NULL),(2,NULL),(3,'X');
SELECT count(*) AS n,count(label) AS known,
 count(DISTINCT label) AS distinct_known FROM code;

Actual SQLite result

Result 1 · 1 rows

nknowndistinct_known
311

SQLite · exit 0

Clause or featureMeaning
UNIQUE and NULLTwo unknown labels do not conflict.
Counting NULLOnly the actual X label contributes to known counts.
Complete standalone SQL and starting fixture
-- SQL-09-006: See how UNIQUE treats NULL
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE code(id INTEGER PRIMARY KEY,label TEXT UNIQUE);
INSERT INTO code VALUES(1,NULL),(2,NULL),(3,'X');
SELECT count(*) AS n,count(label) AS known,
 count(DISTINCT label) AS distinct_known FROM code;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

10 · Writing data, views and triggers

SQL-10-001

Insert a row with RETURNING

RETURNING exposes the row written by the statement. A single-row insert makes its result order unambiguous.

Back to result gallery

SQL to study

INSERT INTO sample VALUES(9,'A',14,'2026-01-10','new')
RETURNING id,site,value;

Actual SQLite result

Result 1 · 1 rows

idsitevalue
9A14.0

SQLite · exit 0

Clause or featureMeaning
INSERT VALUESSupply the new observation.
RETURNINGReturn selected fields of the inserted row.
Complete standalone SQL and starting fixture
-- SQL-10-001: Insert a row with RETURNING
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
INSERT INTO sample VALUES(9,'A',14,'2026-01-10','new')
RETURNING id,site,value;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-10-002

Update one row with RETURNING

A primary-key predicate makes the scope of the update explicit. RETURNING shows the updated value.

Back to result gallery

SQL to study

UPDATE sample SET value=value+2 WHERE id=2
RETURNING id,value;

Actual SQLite result

Result 1 · 1 rows

idvalue
222.0

SQLite · exit 0

Clause or featureMeaning
WHERE id=2Limit the change to one known row.
value+2Calculate from its previous value.
Complete standalone SQL and starting fixture
-- SQL-10-002: Update one row with RETURNING
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
UPDATE sample SET value=value+2 WHERE id=2
RETURNING id,value;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-10-003

Delete one row with RETURNING

The returned fields describe the row that was removed. Other observations remain in the downloadable database.

Back to result gallery

SQL to study

DELETE FROM sample WHERE id=8 RETURNING id,site;

Actual SQLite result

Result 1 · 1 rows

idsite
8D

SQLite · exit 0

Clause or featureMeaning
DELETE WHEREChoose a finite set to remove.
RETURNINGInspect the removed row fields.
Complete standalone SQL and starting fixture
-- SQL-10-003: Delete one row with RETURNING
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
DELETE FROM sample WHERE id=8 RETURNING id,site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-10-004

Handle a conflict with UPSERT

A primary-key conflict updates the existing lookup, while a new key is inserted. A final SELECT defines the display order.

Back to result gallery

SQL to study

INSERT INTO site(id,label) VALUES('A','River East'),('D','Desert')
ON CONFLICT(id) DO UPDATE SET label=excluded.label;
SELECT id,label FROM site ORDER BY id;

Actual SQLite result

Result 1 · 4 rows

idlabel
ARiver East
BHill
CCoast
DDesert

SQLite · exit 0

Clause or featureMeaning
ON CONFLICT(id)Handle the declared unique key.
excluded.labelRead the value proposed by the conflicting insert.
Complete standalone SQL and starting fixture
-- SQL-10-004: Handle a conflict with UPSERT
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
INSERT INTO site(id,label) VALUES('A','River East'),('D','Desert')
ON CONFLICT(id) DO UPDATE SET label=excluded.label;
SELECT id,label FROM site ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-10-005

Reuse a view definition

A view stores a query definition rather than a separate result table. Reading it reflects the current underlying data.

Back to result gallery

SQL to study

CREATE VIEW site_total AS
 SELECT site,sum(value) AS total FROM sample GROUP BY site;
SELECT site,total FROM site_total ORDER BY site;

Actual SQLite result

Result 1 · 4 rows

sitetotal
A36.0
B28.0
C20.0
D16.0

SQLite · exit 0

Clause or featureMeaning
CREATE VIEWName a reusable query.
Not a stored snapshotThe view reads the current sample rows.
Complete standalone SQL and starting fixture
-- SQL-10-005: Reuse a view definition
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE VIEW site_total AS
 SELECT site,sum(value) AS total FROM sample GROUP BY site;
SELECT site,total FROM site_total ORDER BY site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-10-006

Record changes with a trigger

An AFTER UPDATE trigger records old and new values. The audit table is ordinary stored data.

Back to result gallery

SQL to study

CREATE TABLE audit(id INTEGER PRIMARY KEY,sample_id INTEGER,
 old_value REAL,new_value REAL);
CREATE TRIGGER sample_audit AFTER UPDATE OF value ON sample
BEGIN
 INSERT INTO audit(sample_id,old_value,new_value)
 VALUES(old.id,old.value,new.value);
END;
UPDATE sample SET value=13 WHERE id=1;
SELECT sample_id,old_value,new_value FROM audit ORDER BY id;

Actual SQLite result

Result 1 · 1 rows

sample_idold_valuenew_value
112.013.0

SQLite · exit 0

Clause or featureMeaning
AFTER UPDATE OF valueRun after a value update.
old / newRead the row before and after the update.
Complete standalone SQL and starting fixture
-- SQL-10-006: Record changes with a trigger
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE audit(id INTEGER PRIMARY KEY,sample_id INTEGER,
 old_value REAL,new_value REAL);
CREATE TRIGGER sample_audit AFTER UPDATE OF value ON sample
BEGIN
 INSERT INTO audit(sample_id,old_value,new_value)
 VALUES(old.id,old.value,new.value);
END;
UPDATE sample SET value=13 WHERE id=1;
SELECT sample_id,old_value,new_value FROM audit ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

11 · Transactions and savepoints

SQL-11-001

Inspect and roll back a change

The first result sees the uncommitted write on this connection. ROLLBACK restores the original stored value.

Back to result gallery

SQL to study

BEGIN;
UPDATE sample SET value=99 WHERE id=1;
SELECT 'staged' AS phase,value FROM sample WHERE id=1;
ROLLBACK;
SELECT 'after' AS phase,value FROM sample WHERE id=1;

Actual SQLite result

Result 1 · 1 rows

phasevalue
staged99.0

Result 2 · 1 rows

phasevalue
after12.0

SQLite · exit 0

Clause or featureMeaning
BEGINStart an explicit transaction.
Read your writeThis connection sees its pending update.
ROLLBACKUndo the transaction.
Complete standalone SQL and starting fixture
-- SQL-11-001: Inspect and roll back a change
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
BEGIN;
UPDATE sample SET value=99 WHERE id=1;
SELECT 'staged' AS phase,value FROM sample WHERE id=1;
ROLLBACK;
SELECT 'after' AS phase,value FROM sample WHERE id=1;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-11-002

Commit a group of writes

COMMIT makes the inserted observation part of the database state. The summary reads the committed result.

Back to result gallery

Compare with SQL-11-001 · Inspect and roll back a change.

SQL to study

BEGIN;
INSERT INTO sample VALUES(9,'A',14,'2026-01-10','new');
COMMIT;
SELECT count(*) AS n,sum(value) AS total FROM sample;

Actual SQLite result

Result 1 · 1 rows

ntotal
9114.0

SQLite · exit 0

Clause or featureMeaning
COMMITFinish the transaction successfully.
Final summaryCheck the persisted row count and total.
Complete standalone SQL and starting fixture
-- SQL-11-002: Commit a group of writes
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
BEGIN;
INSERT INTO sample VALUES(9,'A',14,'2026-01-10','new');
COMMIT;
SELECT count(*) AS n,sum(value) AS total FROM sample;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-11-003

Roll back to a savepoint

A savepoint gives a partial rollback target inside a transaction. The earlier update survives; the later one is undone.

Back to result gallery

SQL to study

BEGIN;
UPDATE sample SET value=14 WHERE id=1;
SAVEPOINT later;
UPDATE sample SET value=0 WHERE id=2;
ROLLBACK TO later;
RELEASE later; COMMIT;
SELECT id,value FROM sample WHERE id IN(1,2) ORDER BY id;

Actual SQLite result

Result 1 · 2 rows

idvalue
114.0
220.0

SQLite · exit 0

Clause or featureMeaning
SAVEPOINTMark an intermediate transaction state.
ROLLBACK TOUndo later work without ending the outer transaction.
RELEASERemove the savepoint marker.
Complete standalone SQL and starting fixture
-- SQL-11-003: Roll back to a savepoint
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
BEGIN;
UPDATE sample SET value=14 WHERE id=1;
SAVEPOINT later;
UPDATE sample SET value=0 WHERE id=2;
ROLLBACK TO later;
RELEASE later; COMMIT;
SELECT id,value FROM sample WHERE id IN(1,2) ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-11-004

Defer a foreign-key check until commit

A deferred relationship permits the child to be inserted before its parent within one transaction. Both exist when the transaction commits.

Back to result gallery

SQL to study

CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(name TEXT,parent_id INTEGER REFERENCES parent(id)
 DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO child VALUES('pending',42);
INSERT INTO parent VALUES(42);
COMMIT;
SELECT c.name,p.id AS parent_id FROM child AS c
JOIN parent AS p ON p.id=c.parent_id;

Actual SQLite result

Result 1 · 1 rows

nameparent_id
pending42

SQLite · exit 0

Clause or featureMeaning
INITIALLY DEFERREDCheck the relationship at transaction commit.
Commit conditionThe missing parent must exist by the end.
Complete standalone SQL and starting fixture
-- SQL-11-004: Defer a foreign-key check until commit
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(name TEXT,parent_id INTEGER REFERENCES parent(id)
 DEFERRABLE INITIALLY DEFERRED);
BEGIN;
INSERT INTO child VALUES('pending',42);
INSERT INTO parent VALUES(42);
COMMIT;
SELECT c.name,p.id AS parent_id FROM child AS c
JOIN parent AS p ON p.id=c.parent_id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-11-005

Begin a write transaction immediately

BEGIN IMMEDIATE requests the write transaction at the start. This one-connection example shows the statement sequence, not a concurrent-lock benchmark.

Back to result gallery

SQL to study

BEGIN IMMEDIATE;
UPDATE sample SET value=21 WHERE id=1;
COMMIT;
SELECT id,value FROM sample WHERE id=1;

Actual SQLite result

Result 1 · 1 rows

idvalue
121.0

SQLite · exit 0

Clause or featureMeaning
BEGIN IMMEDIATERequest the write transaction up front.
Single connectionNo contention or timeout measurement is claimed here.
Complete standalone SQL and starting fixture
-- SQL-11-005: Begin a write transaction immediately
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
BEGIN IMMEDIATE;
UPDATE sample SET value=21 WHERE id=1;
COMMIT;
SELECT id,value FROM sample WHERE id=1;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-11-006

Select write-ahead logging

WAL is a journal mode of a file-backed database. The native run uses a fresh file and closes the connection before saving its database download.

Back to result gallery

SQL to study

PRAGMA journal_mode=WAL;
BEGIN; UPDATE sample SET value=value+1 WHERE id=1; COMMIT;
SELECT id,value FROM sample WHERE id=1;

Actual SQLite result

Result 1 · 1 rows

journal_mode
wal

Result 2 · 1 rows

idvalue
113.0

SQLite · exit 0

Clause or featureMeaning
journal_mode=WALUse write-ahead logging for this file.
One writerWAL does not provide multiple simultaneous writers.
Portable downloadThe exported data snapshot uses rollback-journal mode so it opens as one file.
Complete standalone SQL and starting fixture
-- SQL-11-006: Select write-ahead logging
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
PRAGMA journal_mode=WAL;
BEGIN; UPDATE sample SET value=value+1 WHERE id=1; COMMIT;
SELECT id,value FROM sample WHERE id=1;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

12 · Indexes and query plans

SQL-12-001

Read a table-scan plan

EXPLAIN QUERY PLAN reports the chosen access strategy. Its numeric fields and wording may change between SQLite versions.

Back to result gallery

SQL to study

EXPLAIN QUERY PLAN
SELECT id,site,value FROM sample WHERE site='A';

Actual SQLite result

Result 1 · 1 rows

idparentnotuseddetail
20216SCAN sample

SQLite · exit 0

Clause or featureMeaning
SCANVisit the table without a selective site index.
Plan versionRead this as a diagnostic, not a stable application interface.
Complete standalone SQL and starting fixture
-- SQL-12-001: Read a table-scan plan
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
EXPLAIN QUERY PLAN
SELECT id,site,value FROM sample WHERE site='A';
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-12-002

Add a selective index

Creating an index changes the available access paths. The captured plan identifies the named index used for the equality predicate.

Back to result gallery

Compare with SQL-12-001 · Read a table-scan plan.

SQL to study

CREATE INDEX ix_site ON sample(site);
EXPLAIN QUERY PLAN
SELECT id,site,value FROM sample WHERE site='A';

Actual SQLite result

Result 1 · 1 rows

idparentnotuseddetail
3062SEARCH sample USING INDEX ix_site (site=?)

SQLite · exit 0

Clause or featureMeaning
CREATE INDEXBuild a search structure on site.
SEARCHUse a restricted index lookup.
ix_siteThe chosen index is named in the native plan.
Complete standalone SQL and starting fixture
-- SQL-12-002: Add a selective index
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE INDEX ix_site ON sample(site);
EXPLAIN QUERY PLAN
SELECT id,site,value FROM sample WHERE site='A';
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-12-003

Use a covering index

The selected columns and filter can be satisfied from this index. The plan explicitly identifies a covering access path.

Back to result gallery

Compare with SQL-12-002 · Add a selective index.

SQL to study

CREATE INDEX ix_site_value ON sample(site,value);
EXPLAIN QUERY PLAN
SELECT site,value FROM sample WHERE site='A' ORDER BY value;

Actual SQLite result

Result 1 · 1 rows

idparentnotuseddetail
3055SEARCH sample USING COVERING INDEX ix_site_value (site=?)

SQLite · exit 0

Clause or featureMeaning
Composite keyStore site followed by value.
COVERINGThe query does not need additional table fields.
Complete standalone SQL and starting fixture
-- SQL-12-003: Use a covering index
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE INDEX ix_site_value ON sample(site,value);
EXPLAIN QUERY PLAN
SELECT site,value FROM sample WHERE site='A' ORDER BY value;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-12-004

Use a composite index prefix and range

Equality on the leading column and a range on the next column fit the index ordering.

Back to result gallery

SQL to study

CREATE INDEX ix_site_day ON sample(site,day);
EXPLAIN QUERY PLAN
SELECT id,day FROM sample
WHERE site='A' AND day>='2026-01-03' ORDER BY day;

Actual SQLite result

Result 1 · 1 rows

idparentnotuseddetail
3047SEARCH sample USING COVERING INDEX ix_site_day (site=? AND day>?)

SQLite · exit 0

Clause or featureMeaning
Leading equalityFix site before traversing the day range.
Range and orderThe second index column supplies the ordered range.
Complete standalone SQL and starting fixture
-- SQL-12-004: Use a composite index prefix and range
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE INDEX ix_site_day ON sample(site,day);
EXPLAIN QUERY PLAN
SELECT id,day FROM sample
WHERE site='A' AND day>='2026-01-03' ORDER BY day;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-12-005

Index a subset of rows

A partial index stores only observations whose note is NULL. The query repeats the condition needed to use that subset.

Back to result gallery

SQL to study

CREATE INDEX ix_missing ON sample(day) WHERE note IS NULL;
EXPLAIN QUERY PLAN
SELECT id,day FROM sample WHERE note IS NULL
 AND day>='2026-01-03' ORDER BY day;

Actual SQLite result

Result 1 · 1 rows

idparentnotuseddetail
40194SEARCH sample USING INDEX ix_missing (day>?)

SQLite · exit 0

Clause or featureMeaning
Partial indexIndex only rows satisfying its WHERE clause.
Matching conditionThe query implies that same condition.
Complete standalone SQL and starting fixture
-- SQL-12-005: Index a subset of rows
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE INDEX ix_missing ON sample(day) WHERE note IS NULL;
EXPLAIN QUERY PLAN
SELECT id,day FROM sample WHERE note IS NULL
 AND day>='2026-01-03' ORDER BY day;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-12-006

Index an expression

An expression index supports a query that uses the same expression. This example indexes normalized lookup labels.

Back to result gallery

SQL to study

CREATE INDEX ix_lower_label ON site(lower(label));
EXPLAIN QUERY PLAN
SELECT id,label FROM site WHERE lower(label)='river';

Actual SQLite result

Result 1 · 1 rows

idparentnotuseddetail
3061SEARCH site USING INDEX ix_lower_label (<expr>=?)

SQLite · exit 0

Clause or featureMeaning
Expression keyStore the lowercased label as the index key.
Matching expressionUse lower(label) in the query predicate.
Complete standalone SQL and starting fixture
-- SQL-12-006: Index an expression
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE INDEX ix_lower_label ON site(lower(label));
EXPLAIN QUERY PLAN
SELECT id,label FROM site WHERE lower(label)='river';
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

13 · JSON documents

SQL-13-001

Extract typed JSON values

JSON extraction and JSON type inspection answer different questions. JSON true and JSON null retain their JSON type names.

Back to result gallery

SQL to study

WITH doc(body) AS (VALUES('{"site":"A","count":3,"checked":true,"missing":null}'))
SELECT json_extract(body,'$.site') AS site,
 json_extract(body,'$.count') AS n,
 json_type(body,'$.checked') AS checked_type,
 json_type(body,'$.missing') AS missing_type FROM doc;

Actual SQLite result

Result 1 · 1 rows

sitenchecked_typemissing_type
A3truenull

SQLite · exit 0

Clause or featureMeaning
json_extractConvert selected scalar values to SQL values.
json_typeDescribe a value using JSON type names.
Complete standalone SQL and starting fixture
-- SQL-13-001: Extract typed JSON values
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH doc(body) AS (VALUES('{"site":"A","count":3,"checked":true,"missing":null}'))
SELECT json_extract(body,'$.site') AS site,
 json_extract(body,'$.count') AS n,
 json_type(body,'$.checked') AS checked_type,
 json_type(body,'$.missing') AS missing_type FROM doc;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-13-002

Compare JSON text and SQL scalars

The arrow operators choose different result representations. JSON null becomes SQL NULL under the scalar extraction operator.

Back to result gallery

SQL to study

WITH doc(body) AS (VALUES('{"n":4,"empty":null}'))
SELECT body->'$.n' AS json_value,body->>'$.n' AS sql_value,
 body->>'$.empty' AS empty,typeof(body->'$.n') AS json_type,
 typeof(body->>'$.n') AS sql_type
FROM doc;

Actual SQLite result

Result 1 · 1 rows

json_valuesql_valueemptyjson_typesql_type
44NULLtextinteger

SQLite · exit 0

Clause or featureMeaning
->Return a JSON text representation.
->>Return a corresponding SQL scalar value.
Complete standalone SQL and starting fixture
-- SQL-13-002: Compare JSON text and SQL scalars
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH doc(body) AS (VALUES('{"n":4,"empty":null}'))
SELECT body->'$.n' AS json_value,body->>'$.n' AS sql_value,
 body->>'$.empty' AS empty,typeof(body->'$.n') AS json_type,
 typeof(body->>'$.n') AS sql_type
FROM doc;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-13-003

Expand a JSON array into rows

json_each is a table-valued function. Array keys become positions, while value and type describe each element.

Back to result gallery

SQL to study

SELECT CAST(key AS INTEGER) AS position,value,type
FROM json_each('["A","B",null]') ORDER BY position;

Actual SQLite result

Result 1 · 3 rows

positionvaluetype
0Atext
1Btext
2NULLnull

SQLite · exit 0

Clause or featureMeaning
json_eachReturn one row per immediate child.
Array keysJSON arrays use zero-based indexes.
Complete standalone SQL and starting fixture
-- SQL-13-003: Expand a JSON array into rows
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT CAST(key AS INTEGER) AS position,value,type
FROM json_each('["A","B",null]') ORDER BY position;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-13-004

Modify and merge JSON objects

json_set edits selected paths. Merge Patch treats a null-valued object member in the patch as a removal request.

Back to result gallery

SQL to study

SELECT json_set('{"site":"A","count":3}',
 '$.count',4,'$.checked',json('true')) AS updated,
 json_patch('{"site":"A","keep":1}',
            '{"site":"B","keep":null}') AS patched;

Actual SQLite result

Result 1 · 1 rows

updatedpatched
{"site":"A","count":4,"checked":true}{"site":"B"}

SQLite · exit 0

Clause or featureMeaning
json(true)Supply a JSON Boolean rather than quoted text.
json_patchApply JSON Merge Patch object semantics.
Complete standalone SQL and starting fixture
-- SQL-13-004: Modify and merge JSON objects
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT json_set('{"site":"A","count":3}',
 '$.count',4,'$.checked',json('true')) AS updated,
 json_patch('{"site":"A","keep":1}',
            '{"site":"B","keep":null}') AS patched;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-13-005

Aggregate ordered values into JSON

An aggregate can produce a JSON array directly from query rows. Ordering inside the aggregate fixes the element order.

Back to result gallery

SQL to study

SELECT json_group_array(site ORDER BY site) AS sites
FROM (SELECT DISTINCT site FROM sample);

Actual SQLite result

Result 1 · 1 rows

sites
["A","B","C","D"]

SQLite · exit 0

Clause or featureMeaning
json_group_arrayBuild a JSON array from selected values.
Ordered aggregateSpecify the array element order.
Complete standalone SQL and starting fixture
-- SQL-13-005: Aggregate ordered values into JSON
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT json_group_array(site ORDER BY site) AS sites
FROM (SELECT DISTINCT site FROM sample);
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-13-006

Store SQLite JSONB

JSONB is SQLite internal binary JSON representation, available since 3.45. It is a BLOB, not the JSONB format of another database system.

Back to result gallery

SQL to study

SELECT typeof(jsonb('{"site":"A","n":3}')) AS storage,
 json(jsonb('{"site":"A","n":3}')) AS text_json,
 json_extract(jsonb('{"site":"A","n":3}'),'$.n') AS n;

Actual SQLite result

Result 1 · 1 rows

storagetext_jsonn
blob{"site":"A","n":3}3

SQLite · exit 0

Clause or featureMeaning
jsonbProduce SQLite binary JSON.
jsonConvert it back to portable JSON text.
CompatibilityRequires SQLite 3.45 or newer.
Complete standalone SQL and starting fixture
-- SQL-13-006: Store SQLite JSONB
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT typeof(jsonb('{"site":"A","n":3}')) AS storage,
 json(jsonb('{"site":"A","n":3}')) AS text_json,
 json_extract(jsonb('{"site":"A","n":3}'),'$.n') AS n;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

14 · Full-text search

SQL-14-001

Search terms with FTS5

An FTS5 virtual table indexes text for MATCH queries. The first example includes its complete corpus creation in the reading view.

Back to result gallery

SQL to study

CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
SELECT rowid,title FROM docs WHERE docs MATCH 'pottery' ORDER BY rowid;

Actual SQLite result

Result 1 · 3 rows

rowidtitle
1River survey
3Coastal notes
4River laboratory

SQLite · exit 0

Clause or featureMeaning
USING fts5Create the full-text virtual table.
MATCHApply full-text query syntax.
FixtureThe remaining chapter examples use this same four-document corpus.
Complete standalone SQL and starting fixture
-- SQL-14-001: Search terms with FTS5
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
SELECT rowid,title FROM docs WHERE docs MATCH 'pottery' ORDER BY rowid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-14-002

Search an exact phrase

A quoted FTS phrase requires the tokens in the stated order. It is different from asking for either word anywhere.

Back to result gallery

Compare with SQL-14-001 · Search terms with FTS5.

SQL to study

SELECT rowid,title FROM docs
WHERE docs MATCH '"river bank"' ORDER BY rowid;

Actual SQLite result

Result 1 · 1 rows

rowidtitle
1River survey

SQLite · exit 0

Clause or featureMeaning
Quoted phraseRequire adjacent matching tokens in order.
SQL quotingSingle quotes contain the FTS query string.
Complete standalone SQL and starting fixture
-- SQL-14-002: Search an exact phrase
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
-- BEGIN EXAMPLE
SELECT rowid,title FROM docs
WHERE docs MATCH '"river bank"' ORDER BY rowid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-14-003

Combine a prefix and an exclusion

A star applies prefix matching to the preceding token. NOT removes documents matching the second term.

Back to result gallery

SQL to study

SELECT rowid,title FROM docs
WHERE docs MATCH 'pot* NOT laboratory' ORDER BY rowid;

Actual SQLite result

Result 1 · 2 rows

rowidtitle
1River survey
3Coastal notes

SQLite · exit 0

Clause or featureMeaning
pot*Match indexed tokens beginning with pot.
NOT laboratoryExclude that term from either indexed column.
Complete standalone SQL and starting fixture
-- SQL-14-003: Combine a prefix and an exclusion
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
-- BEGIN EXAMPLE
SELECT rowid,title FROM docs
WHERE docs MATCH 'pot* NOT laboratory' ORDER BY rowid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-14-004

Order matches by BM25

FTS5 BM25 ranks the matching documents. Better matches have smaller values; rowid breaks ties without displaying unstable score decimals.

Back to result gallery

Compare with SQL-14-001 · Search terms with FTS5.

SQL to study

SELECT rowid,title FROM docs WHERE docs MATCH 'pottery'
ORDER BY bm25(docs),rowid;

Actual SQLite result

Result 1 · 3 rows

rowidtitle
4River laboratory
1River survey
3Coastal notes

SQLite · exit 0

Clause or featureMeaning
bm25(docs)Use the native full-text ranking function.
Ascending scoreSmaller is better in SQLite FTS5.
rowidStabilize equal scores.
Complete standalone SQL and starting fixture
-- SQL-14-004: Order matches by BM25
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
-- BEGIN EXAMPLE
SELECT rowid,title FROM docs WHERE docs MATCH 'pottery'
ORDER BY bm25(docs),rowid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-14-005

Highlight matching tokens

The native highlight function marks matched text. The guide displays those markers as text, without interpreting them as page markup.

Back to result gallery

SQL to study

SELECT rowid,highlight(docs,1,'[',']') AS excerpt
FROM docs WHERE docs MATCH 'pottery' ORDER BY rowid;

Actual SQLite result

Result 1 · 3 rows

rowidexcerpt
1[pottery] fragments near the river bank
3[pottery] and shells from the coastal survey
4[pottery] analysis in the laboratory

SQLite · exit 0

Clause or featureMeaning
Column 1Choose body, the second indexed column.
MarkersSurround each matching token with square brackets.
Complete standalone SQL and starting fixture
-- SQL-14-005: Highlight matching tokens
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
-- BEGIN EXAMPLE
SELECT rowid,highlight(docs,1,'[',']') AS excerpt
FROM docs WHERE docs MATCH 'pottery' ORDER BY rowid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-14-006

Restrict a full-text query by column

Column filters give title and body different roles. Both named conditions must match the same document.

Back to result gallery

SQL to study

SELECT rowid,title FROM docs
WHERE docs MATCH 'title:river AND body:analysis' ORDER BY rowid;

Actual SQLite result

Result 1 · 1 rows

rowidtitle
4River laboratory

SQLite · exit 0

Clause or featureMeaning
title:riverRequire a title token.
body:analysisRequire a body token in the same document.
Complete standalone SQL and starting fixture
-- SQL-14-006: Restrict a full-text query by column
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
CREATE VIRTUAL TABLE docs USING fts5(title,body);
INSERT INTO docs(rowid,title,body) VALUES
 (1,'River survey','pottery fragments near the river bank'),
 (2,'Hill archive','bronze tools from the hill settlement'),
 (3,'Coastal notes','pottery and shells from the coastal survey'),
 (4,'River laboratory','pottery analysis in the laboratory');
-- BEGIN EXAMPLE
SELECT rowid,title FROM docs
WHERE docs MATCH 'title:river AND body:analysis' ORDER BY rowid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

15 · Inspection and database copies

SQL-15-001

Inspect the schema catalog

sqlite_schema exposes stored schema objects. Filter object types deliberately and define the result order.

Back to result gallery

SQL to study

SELECT name,type FROM sqlite_schema
WHERE type='table' ORDER BY name;

Actual SQLite result

Result 1 · 2 rows

nametype
sampletable
sitetable

SQLite · exit 0

Clause or featureMeaning
sqlite_schemaRead schema metadata as rows.
type filterExclude automatically created index objects.
Complete standalone SQL and starting fixture
-- SQL-15-001: Inspect the schema catalog
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT name,type FROM sqlite_schema
WHERE type='table' ORDER BY name;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-15-002

Query column metadata as a table

Table-valued PRAGMAs let metadata participate in ordinary SELECT statements. A rowid-alias primary key is reported differently from an explicit NOT NULL column.

Back to result gallery

SQL to study

SELECT name,type,"notnull" AS required
FROM pragma_table_info('sample') ORDER BY cid;

Actual SQLite result

Result 1 · 5 rows

nametyperequired
idINTEGER0
siteTEXT1
valueREAL1
dayTEXT1
noteTEXT0

SQLite · exit 0

Clause or featureMeaning
pragma_table_infoExpose column metadata as a table-valued function.
required flagReport the declared NOT NULL property.
Complete standalone SQL and starting fixture
-- SQL-15-002: Query column metadata as a table
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT name,type,"notnull" AS required
FROM pragma_table_info('sample') ORDER BY cid;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-15-003

Check declared foreign-key relationships

The table-valued foreign-key check returns violations. The starting fixture deliberately leaves sample.site unconstrained, so missing lookup D is not a declared foreign-key violation.

Back to result gallery

Compare with SQL-05-003 · Find missing relationships.

SQL to study

SELECT count(*) AS violations FROM pragma_foreign_key_check;

Actual SQLite result

Result 1 · 1 rows

violations
0

SQLite · exit 0

Clause or featureMeaning
Declared constraintsOnly schema REFERENCES clauses are checked.
Missing metadataAn unconstrained lookup gap is a separate data-quality question.
Complete standalone SQL and starting fixture
-- SQL-15-003: Check declared foreign-key relationships
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT count(*) AS violations FROM pragma_foreign_key_check;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-15-004

Run a database consistency check

quick_check inspects database consistency. It does not validate every application-level interpretation of the data.

Back to result gallery

SQL to study

PRAGMA quick_check;

Actual SQLite result

Result 1 · 1 rows

quick_check
ok

SQLite · exit 0

Clause or featureMeaning
quick_checkRun the native consistency diagnostic.
ScopeDatabase consistency and domain correctness are different checks.
Complete standalone SQL and starting fixture
-- SQL-15-004: Run a database consistency check
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
PRAGMA quick_check;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-15-005

Inspect affected-row counts

changes() reports the directly affected rows of the most recent data-changing statement on this connection.

Back to result gallery

SQL to study

UPDATE sample SET note='none' WHERE id=-1;
SELECT changes() AS affected;
INSERT INTO sample VALUES(9,'A',14,'2026-01-10','new');
SELECT changes() AS affected;

Actual SQLite result

Result 1 · 1 rows

affected
0

Result 2 · 1 rows

affected
1

SQLite · exit 0

Clause or featureMeaning
changes()Inspect the previous statement direct row count.
Zero-match updateA valid UPDATE can affect no rows.
Complete standalone SQL and starting fixture
-- SQL-15-005: Inspect affected-row counts
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
UPDATE sample SET note='none' WHERE id=-1;
SELECT changes() AS affected;
INSERT INTO sample VALUES(9,'A',14,'2026-01-10','new');
SELECT changes() AS affected;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-15-006

Create and verify a database copy

VACUUM INTO writes a separate database file. Attaching that file makes the copied row count directly inspectable in this fresh working directory.

Back to result gallery

SQL to study

VACUUM INTO 'snapshot.sqlite3';
ATTACH DATABASE 'snapshot.sqlite3' AS snapshot;
SELECT (SELECT count(*) FROM main.sample) AS original_rows,
       (SELECT count(*) FROM snapshot.sample) AS copied_rows;
DETACH DATABASE snapshot;

Actual SQLite result

Result 1 · 1 rows

original_rowscopied_rows
88

SQLite · exit 0

Clause or featureMeaning
VACUUM INTOCreate a compact copy at a new filename.
ATTACHRead a second database through a schema name.
Fresh directoryThe target file must not already contain a database.
Complete standalone SQL and starting fixture
-- SQL-15-006: Create and verify a database copy
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
VACUUM INTO 'snapshot.sqlite3';
ATTACH DATABASE 'snapshot.sqlite3' AS snapshot;
SELECT (SELECT count(*) FROM main.sample) AS original_rows,
       (SELECT count(*) FROM snapshot.sample) AS copied_rows;
DETACH DATABASE snapshot;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

16 · Practical query patterns

SQL-16-001

Pivot a small categorical summary

Conditional aggregation makes a fixed set of categories into columns. The category list is explicit.

Back to result gallery

SQL to study

SELECT sum(CASE WHEN site='A' THEN value ELSE 0 END) AS a,
 sum(CASE WHEN site='B' THEN value ELSE 0 END) AS b,
 sum(CASE WHEN site='C' THEN value ELSE 0 END) AS c,
 sum(CASE WHEN site='D' THEN value ELSE 0 END) AS d FROM sample;

Actual SQLite result

Result 1 · 1 rows

abcd
36.028.020.016.0

SQLite · exit 0

Clause or featureMeaning
Conditional aggregateContribute only the selected category value.
Fixed columnsAdd another expression for a new category.
Complete standalone SQL and starting fixture
-- SQL-16-001: Pivot a small categorical summary
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
SELECT sum(CASE WHEN site='A' THEN value ELSE 0 END) AS a,
 sum(CASE WHEN site='B' THEN value ELSE 0 END) AS b,
 sum(CASE WHEN site='C' THEN value ELSE 0 END) AS c,
 sum(CASE WHEN site='D' THEN value ELSE 0 END) AS d FROM sample;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-16-002

Find islands of consecutive dates

Subtracting an ordered row number from each distinct day gives a constant key within a run of consecutive dates.

Back to result gallery

SQL to study

WITH distinct_days AS (SELECT DISTINCT day FROM sample),
 keyed AS (SELECT day,julianday(day)-row_number() OVER(ORDER BY day) AS k
           FROM distinct_days)
SELECT min(day) AS start,max(day) AS end,count(*) AS days
FROM keyed GROUP BY k ORDER BY start;

Actual SQLite result

Result 1 · 2 rows

startenddays
2026-01-012026-01-066
2026-01-082026-01-092

SQLite · exit 0

Clause or featureMeaning
Distinct datesRemove duplicate dates before numbering.
Island keyA one-day increase and one-row increase cancel.
Complete standalone SQL and starting fixture
-- SQL-16-002: Find islands of consecutive dates
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH distinct_days AS (SELECT DISTINCT day FROM sample),
 keyed AS (SELECT day,julianday(day)-row_number() OVER(ORDER BY day) AS k
           FROM distinct_days)
SELECT min(day) AS start,max(day) AS end,count(*) AS days
FROM keyed GROUP BY k ORDER BY start;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-16-003

Return the top two within each group

A CTE allows the window result to be filtered in an outer query. Ties are resolved by the primary key.

Back to result gallery

Compare with SQL-07-001 · Number rows within each site.

SQL to study

WITH ranked AS (SELECT id,site,value,row_number() OVER(
 PARTITION BY site ORDER BY value DESC,id) AS position FROM sample)
SELECT id,site,value FROM ranked WHERE position<=2
ORDER BY site,position;

Actual SQLite result

Result 1 · 7 rows

idsitevalue
3A18.0
1A12.0
2B20.0
4B8.0
5C10.0
7C10.0
8D16.0

SQLite · exit 0

Clause or featureMeaning
Outer filterFilter a window result in a subsequent query stage.
At most twoSites with fewer observations return fewer rows.
Complete standalone SQL and starting fixture
-- SQL-16-003: Return the top two within each group
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH ranked AS (SELECT id,site,value,row_number() OVER(
 PARTITION BY site ORDER BY value DESC,id) AS position FROM sample)
SELECT id,site,value FROM ranked WHERE position<=2
ORDER BY site,position;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-16-004

Deduplicate an explicit ingestion key

This separate incoming table defines batch plus code as its business key. Keep the earliest row for that key; do not infer that equal observation values are duplicates.

Back to result gallery

SQL to study

CREATE TABLE incoming(id INTEGER PRIMARY KEY,batch TEXT,code TEXT,value INTEGER);
INSERT INTO incoming VALUES(1,'b1','A',12),(2,'b1','A',12),
 (3,'b1','B',20),(4,'b2','A',18);
DELETE FROM incoming WHERE id IN(
 SELECT id FROM (SELECT id,row_number() OVER(
  PARTITION BY batch,code ORDER BY id) AS position FROM incoming)
 WHERE position>1);
SELECT id,batch,code,value FROM incoming ORDER BY id;

Actual SQLite result

Result 1 · 3 rows

idbatchcodevalue
1b1A12
3b1B20
4b2A18

SQLite · exit 0

Clause or featureMeaning
Business keyDeduplication depends on the declared meaning of batch and code.
Earliest idChoose the retained row deterministically.
Complete standalone SQL and starting fixture
-- SQL-16-004: Deduplicate an explicit ingestion key
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
CREATE TABLE incoming(id INTEGER PRIMARY KEY,batch TEXT,code TEXT,value INTEGER);
INSERT INTO incoming VALUES(1,'b1','A',12),(2,'b1','A',12),
 (3,'b1','B',20),(4,'b2','A',18);
DELETE FROM incoming WHERE id IN(
 SELECT id FROM (SELECT id,row_number() OVER(
  PARTITION BY batch,code ORDER BY id) AS position FROM incoming)
 WHERE position>1);
SELECT id,batch,code,value FROM incoming ORDER BY id;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-16-005

Calculate a calendar-based rolling total

Generate missing dates before using a ROWS window. Zero is an explicit modelling choice for a day with no observation.

Back to result gallery

Compare with SQL-07-004 · Calculate a three-row moving mean.

SQL to study

WITH RECURSIVE days(day) AS (
 VALUES('2026-01-01') UNION ALL
 SELECT date(day,'+1 day') FROM days WHERE day<'2026-01-09'
), filled AS (SELECT d.day,coalesce(s.value,0) AS value
 FROM days AS d LEFT JOIN sample AS s ON s.day=d.day)
SELECT day,value,sum(value) OVER(ORDER BY day
 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS total3
FROM filled ORDER BY day;

Actual SQLite result

Result 1 · 9 rows

dayvaluetotal3
2026-01-0112.012.0
2026-01-0220.032.0
2026-01-0318.050.0
2026-01-048.046.0
2026-01-0510.036.0
2026-01-066.024.0
2026-01-07016.0
2026-01-0810.016.0
2026-01-0916.026.0

SQLite · exit 0

Clause or featureMeaning
Calendar firstInsert the missing seventh day before windowing.
Zero fillTreat no observation as zero only for this stated model.
One row per dayThis fixture has at most one observation on each day.
Complete standalone SQL and starting fixture
-- SQL-16-005: Calculate a calendar-based rolling total
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH RECURSIVE days(day) AS (
 VALUES('2026-01-01') UNION ALL
 SELECT date(day,'+1 day') FROM days WHERE day<'2026-01-09'
), filled AS (SELECT d.day,coalesce(s.value,0) AS value
 FROM days AS d LEFT JOIN sample AS s ON s.day=d.day)
SELECT day,value,sum(value) OVER(ORDER BY day
 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS total3
FROM filled ORDER BY day;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

SQL-16-006

Compose a joined summary report

A grouped CTE, lookup join and scalar total combine into one report. The missing lookup remains visible rather than disappearing.

Back to result gallery

SQL to study

WITH totals AS (SELECT site,count(*) AS n,sum(value) AS total
 FROM sample GROUP BY site)
SELECT t.site,coalesce(s.label,'(unmapped)') AS label,t.n,t.total,
 round(t.total/(SELECT sum(value) FROM sample),3) AS share
FROM totals AS t LEFT JOIN site AS s ON s.id=t.site
ORDER BY t.total DESC,t.site;

Actual SQLite result

Result 1 · 4 rows

sitelabelntotalshare
ARiver336.00.36
BHill228.00.28
CCoast220.00.2
D(unmapped)116.00.16

SQLite · exit 0

Clause or featureMeaning
Grouped CTESet the report unit to one site.
LEFT JOINRetain the unlabelled site.
Scalar denominatorUse the total over all observations.
Complete standalone SQL and starting fixture
-- SQL-16-006: Compose a joined summary report
-- Run against a fresh database; all inputs are synthetic.
PRAGMA foreign_keys=ON;
CREATE TABLE sample(
 id INTEGER PRIMARY KEY, site TEXT NOT NULL,
 value REAL NOT NULL, day TEXT NOT NULL, note TEXT
);
INSERT INTO sample VALUES
 (1,'A',12,'2026-01-01',NULL),
 (2,'B',20,'2026-01-02','checked'),
 (3,'A',18,'2026-01-03',NULL),
 (4,'B',8,'2026-01-04','recheck'),
 (5,'C',10,'2026-01-05',NULL),
 (6,'A',6,'2026-01-06','checked'),
 (7,'C',10,'2026-01-08','archived'),
 (8,'D',16,'2026-01-09',NULL);
CREATE TABLE site(id TEXT PRIMARY KEY,label TEXT NOT NULL);
INSERT INTO site VALUES ('A','River'),('B','Hill'),('C','Coast');
-- BEGIN EXAMPLE
WITH totals AS (SELECT site,count(*) AS n,sum(value) AS total
 FROM sample GROUP BY site)
SELECT t.site,coalesce(s.label,'(unmapped)') AS label,t.n,t.total,
 round(t.total/(SELECT sum(value) FROM sample),3) AS share
FROM totals AS t LEFT JOIN site AS s ON s.id=t.site
ORDER BY t.total DESC,t.site;
-- END EXAMPLE

Download: Standalone SQL · Captured results · Data snapshot

Example and command index

Content: CC0 1.0. Structure and publishing framework: CC BY 4.0 by Lukas C. Bossert. Repository source · Publishing framework source · License scope and third-party notices.