Everything from the course on one page: from your first SELECT to JOINs, window functions and changing data. Every example query works on the video store dataset from the lessons (films, members and rentals).
Tip: print this page or save it as a PDF.
SELECT * FROM filmsSELECT title, year FROM filmsSELECT title AS film FROM filmsAS gives a column another name in the result.SELECT title, price_per_day * 2 AS weekend FROM films+, -, * and /, row by row.SELECT title FROM films LIMIT 5ORDER BY the database picks which.SELECT title FROM films LIMIT 5 OFFSET 10OFFSET skips rows first: handy for showing results page by page.SELECT title FROM films WHERE genre = 'horror'= cares about upper and lower case.SELECT title FROM films WHERE minutes >= 120=, <> (or !=), <, <=, >, >=. Numbers without quotes.SELECT * FROM rentals WHERE rented_on > '2024-10-15''year-month-day', always between quotes.SELECT title FROM films WHERE (genre = 'drama' OR genre = 'comedy') AND year > 2000AND comes before OR: put brackets around what belongs together.SELECT title FROM films WHERE NOT genre = 'animation'NOT turns a condition around.SELECT name FROM members WHERE town IN ('Leuven', 'Gent')IN is a short OR. NOT IN is the opposite.SELECT title FROM films WHERE year BETWEEN 1990 AND 1999BETWEEN includes both borders. Smallest value first.SELECT title FROM films WHERE title LIKE 'The %'% is zero or more characters, _ exactly one. In SQLite LIKE ignores upper and lower case.SELECT name FROM members WHERE email IS NULLIS NULL or IS NOT NULL, never with = NULL.SELECT title, score FROM films ORDER BY score DESCORDER BY sorts, ASC ascending (the default), DESC descending.SELECT title, year FROM films ORDER BY year DESC LIMIT 3SELECT genre, title, score FROM films ORDER BY genre, score DESCNULL comes first when sorting ascending.SELECT DISTINCT genre FROM filmsDISTINCT removes duplicate rows. With several columns, the combination counts.SELECT COUNT(*) FROM filmsCOUNT(*) counts rows. You get one row with one number.SELECT COUNT(score), COUNT(DISTINCT genre) FROM filmsCOUNT(column) skips NULL, COUNT(DISTINCT column) counts different values.SELECT SUM(fine), AVG(fine) FROM rentalsSUM adds up, AVG averages. Both skip NULL. Without rows, SUM gives NULL, not 0.SELECT MIN(year), MAX(year) FROM filmsSELECT ROUND(AVG(score), 1) FROM filmsROUND(number, decimals) rounds. Watch out: 7 / 2 gives 3 in SQLite; write 7 * 1.0 / 2 for 3.5.SELECT genre, COUNT(*) AS films FROM films GROUP BY genreSELECT is in GROUP BY or inside a function such as COUNT.SELECT genre, COUNT(*) FROM films GROUP BY genre HAVING COUNT(*) >= 4WHERE filters rows before grouping, HAVING filters groups afterwards.SELECT genre, AVG(score) AS average FROM films WHERE year > 1990 GROUP BY genre ORDER BY average DESCSELECT town, plan, COUNT(*) FROM members GROUP BY town, planThe table before JOIN is on the left, the table after it on the right. The coloured part is what stays.
SELECT f.title, r.rented_on FROM rentals r JOIN films f ON r.film_id = f.idJOIN (or INNER JOIN): only rows with a partner on both sides. r and f are aliases.SELECT f.title, r.id FROM films f LEFT JOIN rentals r ON r.film_id = f.idLEFT JOIN: every row on the left, also without a partner. The right side is then NULL.SELECT f.title, r.id FROM rentals r RIGHT JOIN films f ON r.film_id = f.idRIGHT JOIN: every row on the right. The same as a LEFT JOIN with the tables swapped.SELECT m.name, r.id FROM members m FULL JOIN rentals r ON r.member_id = m.idFULL JOIN: every row of both sides, with NULL where a partner is missing.SELECT f.title FROM films f LEFT JOIN rentals r ON r.film_id = f.id WHERE r.id IS NULLLEFT JOIN and then IS NULL on the right side. Here: films that were never rented.SELECT m.name, f.title FROM rentals r JOIN members m ON r.member_id = m.id JOIN films f ON r.film_id = f.idJOINs in a row, each with its own ON.SELECT a.name, b.name FROM members a JOIN members b ON a.town = b.town AND a.id < b.idSELECT title, CASE WHEN score >= 8 THEN 'top' WHEN score >= 7 THEN 'good' ELSE 'so-so' END AS verdict FROM filmsCASE picks a value per row, like an if-then. ELSE catches the rest, NULL included.SELECT title FROM films WHERE score > (SELECT AVG(score) FROM films)SELECT name FROM members WHERE id IN (SELECT member_id FROM rentals WHERE fine > 0)IN gives a list. Here the members who ever got a fine.WITH popular AS (SELECT film_id, COUNT(*) AS times FROM rentals GROUP BY film_id) SELECT f.title, p.times FROM popular p JOIN films f ON p.film_id = f.idWITH (a CTE) gives an intermediate result a name, so your query stays readable.SELECT name FROM members WHERE town = 'Gent' UNION SELECT name FROM members WHERE plan = 'premium'UNION stacks results and removes duplicates. UNION ALL keeps them.SELECT UPPER(title), LOWER(genre), LENGTH(title) FROM filmsSELECT name || ' (' || town || ')' AS member FROM members|| glues text together.SELECT SUBSTR(title, 1, 3), REPLACE(title, ' ', '_'), TRIM(' yes ') FROM filmsSELECT title FROM films WHERE LOWER(genre) = 'horror'SELECT rented_on, strftime('%Y', rented_on) AS year, date(rented_on, '+7 days') AS due FROM rentalsstrftime takes part of a date, date(..., '+7 days') calculates with it. date('now') is today.SELECT julianday(returned_on) - julianday(rented_on) AS days FROM rentalsSELECT name, COALESCE(email, 'no e-mail') FROM membersCOALESCE gives the first value that isn't NULL: handy as a fallback.Calculating over several rows, without merging them into one row as GROUP BY does.
SELECT title, score, ROW_NUMBER() OVER (ORDER BY score DESC) AS place FROM filmsROW_NUMBER() numbers the rows in the order of OVER (ORDER BY ...).SELECT title, genre, score, RANK() OVER (PARTITION BY genre ORDER BY score DESC) AS place FROM filmsPARTITION BY starts again for every genre. RANK() gives equal scores the same place.SELECT rented_on, fine, SUM(fine) OVER (ORDER BY rented_on) AS total FROM rentalsIn Lurndo you practise without risk. After every query the database resets itself.
INSERT INTO members (name, town, birth_year, member_since, plan) VALUES ('Ann Peeters', 'Lier', 1985, '2025-10-01', 'basic')NULL (or their default value).UPDATE films SET price_per_day = price_per_day + 0.5 WHERE year >= 2020DELETE FROM rentals WHERE returned_on IS NOT NULL AND fine = 0BEGIN; UPDATE members SET plan = 'premium' WHERE id = 1; COMMIT;COMMIT saves, ROLLBACK undoes everything.CREATE TABLE reservations (id INTEGER PRIMARY KEY, film_id INTEGER NOT NULL REFERENCES films (id), name TEXT NOT NULL, reserved_on TEXT DEFAULT (date('now')))INTEGER, REAL, TEXT, BLOB. PRIMARY KEY, NOT NULL, DEFAULT and REFERENCES (foreign key) guard your data.ALTER TABLE members ADD COLUMN phone TEXTDROP TABLE rentalsCREATE INDEX idx_films_genre ON films (genre)CREATE VIEW recent_films AS SELECT title, year FROM films WHERE year >= 2020; SELECT * FROM recent_filmsSELECT genre, COUNT(*) AS films FROM films WHERE year >= 2000 GROUP BY genre HAVING COUNT(*) > 1 ORDER BY films DESC LIMIT 3SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. The database works in a different order: first FROM and JOIN, then WHERE, GROUP BY, HAVING, then SELECT, and only at the end ORDER BY and LIMIT. That is why an alias works in ORDER BY, but not in WHERE.