SQL Cheat Sheet

SQL cheat sheet: syntax and examples of common statements — SELECT, JOIN, GROUP BY, INSERT, UPDATE, DELETE, aggregates and constraints — for quick lookup while writing queries.

How It Works

SQL is the standard declarative language for relational databases: you state what you want and the optimizer decides how to fetch it. DML (SELECT/INSERT/UPDATE/DELETE) handles data, DDL (CREATE/ALTER/DROP) defines structure, DCL (GRANT) manages rights.

Data lives in tables (rows/columns) linked by foreign keys; JOIN assembles multiple tables into a result set. Indexes speed reads but slow writes; transactions guarantee concurrency safety via ACID (Atomic, Consistent, Isolated, Durable). Prefer parameterized queries to prevent SQL injection.

When to Use This Table

This table is for looking up SQL syntax without opening documentation. Its value is coverage: DDL, DML, aggregates, window functions and transaction control are grouped so a missing piece is easy to locate. Dialect differences are the catch, though — the same "top N rows" is LIMIT in MySQL, TOP n in SQL Server and ROWNUM in Oracle, so cross-database work requires translation.

When not to consult it: you are tuning query performance. Index types, execution plans and cost models belong to database tuning, and a syntactically correct full-table scan is far slower than a syntax error. This table offers no performance criteria at all.

Rule of thumb: this table is enough for syntax assembly, report queries and one-off exports. For isolation levels, locking behaviour and concurrent writes, read your specific database documentation instead of generic syntax — the same UPDATE can behave completely differently under different isolation levels.

Mind SQL injection: string concatenation instead of parameter binding remains the most common backend vulnerability. The syntax here is not itself a problem, but any writing that interpolates a variable should use a prepared statement rather than wrapping the value in quotes.

Boundaries and Common Pitfalls

  • NULL in any comparison yields NULL

    This is the most counter-intuitive rule: NULL <> 1 evaluates to NULL, not true, so WHERE filters the row out. Write IS NULL or IS NOT NULL explicitly. Aggregates also ignore NULL (COUNT(column) skips nulls while COUNT(*) counts rows), and AVG or SUM over an all-NULL set returns NULL rather than zero.

  • DELETE and TRUNCATE differ far more than expected

    DELETE removes row by row inside a transaction, is rollback-capable and can target a subset via WHERE. TRUNCATE wipes the whole table, is usually not rollback-capable depending on database and engine, resets identity columns, and does not fire DELETE triggers. With foreign keys TRUNCATE typically fails outright while DELETE can proceed from child tables first. Mismatching them loses production data or fails unexpectedly.

  • An UPDATE without WHERE empties the table

    This happens every year. Practical guards: run the equivalent SELECT first to see the affected row count; configure the database to reject or require explicit confirmation for a WHERE-less UPDATE; or restrict UPDATE and DELETE to views or stored procedures that require a condition. Relying on care alone does not work.

  • NULL semantics in JOINs are easy to miss

    Writing a right-table condition in WHERE instead of ON silently degrades a LEFT JOIN into an INNER JOIN, because NULL <> x is NULL and therefore not true. Conditions belong in the ON clause. Separately, COUNT(right.column) under a LEFT JOIN does not count unmatched rows, so counting total rows requires COUNT(*) rather than COUNT(column) — a frequent root cause of mismatched report totals.

How This Relates to Neighbours

For dialect differences and version-specific behaviour of MySQL, PostgreSQL, SQLite or SQL Server, consult that database official docs; this table only covers common syntax.

To format or validate a statement, use the in-site SQL tools; this table has no parsing or highlighting.

Understanding why an index is not used and reading EXPLAIN requires database tuning knowledge beyond this table.

If the goal is safely exporting results (quoting, delimiters, encoding), that is an export pipeline problem rather than a SQL syntax one.

Practical Snippet

Key points: (1) run the equivalent SELECT first to check the affected row count, wrap the write in a transaction and commit only after verifying; (2) NULL can only be tested with IS NULL — = NULL is never true; (3) right-side conditions in a LEFT JOIN belong in ON, since putting them in WHERE degrades it to an INNER JOIN, and count rows with COUNT(*) not COUNT(column); (4) always bind parameters — string concatenation is the main SQL injection vector.

-- ① 改写前先看影响面:把 UPDATE 的 WHERE 套进 SELECT
SELECT id, name, status
FROM   orders
WHERE  status = 'pending' AND created_at < '2024-01-01';
-- 确认行数合理,再执行 UPDATE;行数异常就是条件写错了

BEGIN;
UPDATE orders SET status = 'expired'
WHERE  status = 'pending' AND created_at < '2024-01-01';
-- 核对受影响行数与上一步一致
COMMIT;  -- 不满意就 ROLLBACK;

-- ② NULL 必须显式判断
SELECT name FROM users WHERE manager_id IS NULL;      -- 不是 = NULL
SELECT name FROM users WHERE manager_id IS NOT NULL;

-- ③ LEFT JOIN 的条件写在 ON,不要写 WHERE
SELECT c.name AS customer, o.total
FROM   customers c
LEFT JOIN orders o
       ON o.customer_id = c.id
      AND o.status = 'paid';          -- 放在 ON 里才保留未付费客户
-- 统计总行数用 COUNT(*),不是 COUNT(o.total)
SELECT COUNT(*) AS customers, COUNT(o.total) AS paid_rows
FROM   customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid';

-- ④ 用参数绑定,不要拼字符串(SQL 注入防线)
PREPARE stmt FROM 'SELECT * FROM users WHERE email = ?';
SET @email = 'a@example.com';
EXECUTE stmt USING @email;
DEALLOCATE PREPARE stmt;

Practical Notes

  • Any comparison with NULL yields UNKNOWNWHERE col = NULL never matches; use IS NULL. Likewise, if a NOT IN subquery returns any NULL, the whole predicate becomes UNKNOWN and the result set comes back empty — a classic "the rows exist but the query finds nothing" bug.
  • COUNT(*) and COUNT(col) mean different thingsCOUNT(*) counts rows; COUNT(col) counts only rows where that column is not NULL. Mixing them up skews null-rate analysis, and COUNT(DISTINCT col) drops NULLs as well.
  • Implicit casts disable indexesWHERE varchar_col = 123 makes most databases cast the column side to a number, so the index is unusable and the query becomes a full scan. Keep parameter types aligned with column types, including collation and character set.

Data Sources

  • ISO/IEC 9075 (SQL)
  • Database vendor dialect documentation

Codes, names and definitions above come from public specifications; we claim no originality over the specification data itself. The arrangement, Chinese annotations and practical notes on this page are written by us.

StatementMeaningExample
SELECTSelect columns (* for all)SELECT id, name FROM users
FROMSpecify source tableFROM orders
WHERERow filter conditionWHERE status = 'paid'
GROUP BYGroup rows for aggregationGROUP BY category
HAVINGFilter grouped resultsHAVING COUNT(*) > 1
ORDER BYSort (ASC/DESC)ORDER BY created_at DESC
LIMITLimit number of rowsLIMIT 10
OFFSETSkip first N rows (paging)OFFSET 20
DISTINCTReturn distinct valuesSELECT DISTINCT city
JOINInner join two tablesFROM a JOIN b ON a.id = b.a_id
LEFT JOINLeft join, keep all left rowsFROM a LEFT JOIN b ON a.id = b.a_id
RIGHT JOINRight join, keep all right rowsFROM a RIGHT JOIN b ON a.id = b.a_id
INNER JOINInner join (same as JOIN)FROM a INNER JOIN b ON a.id = b.a_id
ONJoin conditionON a.id = b.a_id
ASTable or column aliasSELECT name AS full_name
INSERT INTOInsert a rowINSERT INTO users (name) VALUES ('Tom')
UPDATEUpdate rowsUPDATE users SET name = 'Tom' WHERE id = 1
DELETEDelete rowsDELETE FROM users WHERE id = 1
CREATE TABLECreate a tableCREATE TABLE t (id INT PRIMARY KEY)
ALTER TABLEAlter table structureALTER TABLE t ADD COLUMN age INT
DROP TABLEDrop a tableDROP TABLE t
PRIMARY KEYPrimary key constraintid INT PRIMARY KEY
FOREIGN KEYForeign key constraintFOREIGN KEY (a_id) REFERENCES a(id)
INDEXCreate index to speed up queryCREATE INDEX idx_name ON users(name)
UNIONCombine two result sets (distinct)SELECT a FROM t1 UNION SELECT a FROM t2
LIKEPattern match (% any, _ one)WHERE name LIKE 'T%'
INValue in a setWHERE status IN ('paid','done')
BETWEENInclusive rangeWHERE age BETWEEN 18 AND 30
NULL / IS NULLNull check (cannot use =)WHERE deleted_at IS NULL
COUNT / SUM / AVGAggregate functionsSELECT COUNT(*) FROM users
CASE WHENConditional expressionCASE WHEN age>18 THEN 'adult' ELSE 'minor' END
EXPLAINShow execution planEXPLAIN SELECT * FROM users

Frequently Asked Questions

What is the difference between INNER JOIN and LEFT JOIN?

INNER JOIN returns only rows matching in both tables; LEFT JOIN returns all rows from the left table, filling right-side columns with NULL when unmatched. Use LEFT JOIN to keep every master record, INNER JOIN for the intersection.

What is the difference between WHERE and HAVING?

WHERE filters rows before grouping; HAVING filters groups after GROUP BY and can reference aggregates (e.g. COUNT(*)). You cannot use aggregate functions in WHERE.

What is the difference between % and _ in LIKE?

% matches any length (including zero) of characters; _ matches exactly one character. 'T%' matches any string starting with T; 'T_m' matches T, one char, then m (e.g. Tom).

How do I test for NULL?

NULL means unknown and cannot be tested with = NULL; use IS NULL / IS NOT NULL. In most databases comparing NULL to anything yields unknown (not true), which is a common trap.