SQL Cheatsheet
SQL asks a database questions (and sometimes changes rows). Start with SELECT, then add filters, sorts, and joins as your questions get more specific.
Practice on a disposable database first. Prefer precise WHERE clauses before UPDATE or DELETE, and use transactions when a change must succeed or fail as a unit.
Full lessons: SQL Tutorials
Basics
What SQL does
Structured Query Language describes what data you want; the database engine figures out how to fetch or change it.
-- Ask for rows matching a condition
SELECT name FROM users WHERE active = 1;
Tables and rows
A table is a named grid: columns define fields, each row is one record.
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT
);
Primary key
A column (or set of columns) that uniquely identifies each row—often an auto-incrementing id.
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
);
NULL
Means “unknown or missing,” not the number zero or an empty string. Compare with IS NULL / IS NOT NULL.
SELECT * FROM users WHERE email IS NULL;
Comments
Document queries with -- line comments or /* ... */ blocks.
-- Count active accounts
SELECT COUNT(*) FROM users WHERE active = 1;
Reading data
SELECT *
Return every column. Fine for exploration; prefer naming columns in real apps.
SELECT * FROM users;
SELECT columns
List only the fields you need—clearer and often faster.
SELECT id, name, email FROM users;
DISTINCT
Remove duplicate values from the result set for the selected columns.
SELECT DISTINCT city FROM customers;
Aliases (AS)
Rename a column or table in the result for readability.
SELECT name AS full_name, created_at AS joined
FROM users;
LIMIT
Cap how many rows come back—great while testing or paging results.
SELECT * FROM products ORDER BY price DESC LIMIT 10;
Filtering & sorting
WHERE
Keep only rows that match a condition.
SELECT * FROM orders WHERE status = 'shipped';
AND / OR / NOT
Combine or negate conditions. Use parentheses when mixing AND and OR.
SELECT * FROM products
WHERE in_stock = 1 AND (price < 20 OR on_sale = 1);
LIKE
Match text patterns. % means any sequence; _ means one character.
SELECT * FROM users WHERE email LIKE '%@example.com';
BETWEEN / IN
BETWEEN is inclusive range; IN matches any value in a list.
SELECT * FROM products WHERE price BETWEEN 10 AND 50;
SELECT * FROM orders WHERE status IN ('new', 'paid');
ORDER BY
Sort results ascending (ASC, default) or descending (DESC).
SELECT name, score FROM players ORDER BY score DESC, name ASC;
Aggregate + GROUP BY
Summarize groups with COUNT, SUM, AVG, and friends.
SELECT city, COUNT(*) AS customers
FROM customers
GROUP BY city
ORDER BY customers DESC;
Joins
Why join
Combine rows from related tables using a shared key (for example user_id).
SELECT users.name, orders.total
FROM users
INNER JOIN orders ON orders.user_id = users.id;
INNER JOIN
Return only rows that have a match in both tables.
SELECT p.title, c.name AS category
FROM posts p
INNER JOIN categories c ON c.id = p.category_id;
LEFT JOIN
Keep every row from the left table; missing right-side matches show as NULL.
SELECT u.name, o.id AS order_id
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;
Foreign key idea
A column that points at another table’s primary key keeps relationships honest.
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
total REAL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
Self-check after join
If row counts explode, you may have matched too loosely—inspect join keys and sample results.
SELECT COUNT(*) FROM orders;
SELECT COUNT(*) FROM users u
INNER JOIN orders o ON o.user_id = u.id;
Changing data
INSERT
Add one or more new rows. List columns explicitly when you can.
INSERT INTO users (name, email)
VALUES ('Sam Lee', '[email protected]');
UPDATE
Change existing rows. Always include a careful WHERE unless you truly mean every row.
UPDATE users SET active = 0 WHERE last_login < '2024-01-01';
DELETE
Remove matching rows. Preview with SELECT using the same WHERE first.
SELECT * FROM sessions WHERE expires_at < CURRENT_TIMESTAMP;
DELETE FROM sessions WHERE expires_at < CURRENT_TIMESTAMP;
CREATE TABLE
Define a new table and its columns before you insert data.
CREATE TABLE tags (
id INTEGER PRIMARY KEY,
label TEXT UNIQUE NOT NULL
);
Transactions
Group statements so they all commit together or all roll back on failure.
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
UPDATE accounts SET balance = balance + 50 WHERE id = 2;
COMMIT;
Design tips
Normalize lightly
Store each fact once (users in one table, orders in another) and link with keys instead of copying names everywhere.
-- Prefer user_id on orders, not repeating full user name on every order row
Useful indexes
Index columns you filter or join on often. Too many indexes slow writes.
CREATE INDEX idx_orders_user_id ON orders(user_id);
Views
Save a named query you reuse often without duplicating SQL in every app call.
CREATE VIEW active_users AS
SELECT id, name, email FROM users WHERE active = 1;
Avoid SELECT * in apps
Name columns so schema changes and large text fields do not surprise your code.
SELECT id, name FROM users WHERE id = 42;
Back up before bulk edits
Export or snapshot before mass UPDATE/DELETE. Mistakes are cheap to undo from a backup.
-- SQLite example
.backup main backup.db
Comments
One comment per signed-in account. Comments are saved with this page’s URL.