| |

SQL 131 🛢️ Cheatsheet

⏱️ Estimated read: 7 min | |

A quick-reference for SQL syntax, functions, and behavior across PostgreSQL, MySQL, and SQL Server. Organized for lookup, not for reading.


SELECT Clause Order

ClausePurposeEvaluated
SELECTColumns to return5th
FROMTables to query1st
JOINCombine tables2nd
WHEREFilter rows3rd
GROUP BYGroup rows4th
HAVINGFilter groups6th
ORDER BYSort result7th
LIMIT / OFFSETLimit rows8th

WHERE cannot use aliases defined in SELECT; ORDER BY can.


JOIN Types

JoinReturns
INNER JOINRows matching in both tables
LEFT JOINAll left rows + matching right (NULL if none)
RIGHT JOINAll right rows + matching left
FULL OUTER JOINAll rows from both tables
CROSS JOINCartesian product
NATURAL JOINJoin on same-named columns
SELF JOINTable joined to itself
-- LEFT JOIN with NULL handling
SELECT u.id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;  -- users with no orders

Aggregation

FunctionReturns
COUNT(*)Row count
COUNT(col)Non-NULL count
COUNT(DISTINCT col)Unique non-NULL count
SUM(col)Sum
AVG(col)Average
MIN(col) / MAX(col)Min/max
STRING_AGG(col, ',')Concatenate (PostgreSQL)
GROUP_CONCAT(col)Concatenate (MySQL)
ARRAY_AGG(col)Array (PostgreSQL)
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 5;

Window Functions

FunctionPurpose
ROW_NUMBER()Sequential number, no ties
RANK()Rank with gaps for ties
DENSE_RANK()Rank without gaps
NTILE(n)Divide into n buckets
LAG(col, n)Previous row value
LEAD(col, n)Next row value
SUM(...) OVER (...)Running total
FIRST_VALUE(col)First value in frame
LAST_VALUE(col)Last value in frame
SELECT
  user_id,
  total,
  ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders;

-- Top-1 per group
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
  FROM orders
) t WHERE rn = 1;

CTEs and Recursion

-- Basic CTE
WITH recent_orders AS (
  SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days'
)
SELECT user_id, COUNT(*) FROM recent_orders GROUP BY user_id;

-- Recursive CTE
WITH RECURSIVE tree AS (
  SELECT id, parent_id, 1 AS depth FROM nodes WHERE parent_id IS NULL
  UNION ALL
  SELECT n.id, n.parent_id, t.depth + 1
  FROM nodes n JOIN tree t ON n.parent_id = t.id
)
SELECT * FROM tree;

DML

StatementPurpose
INSERT INTO t (cols) VALUES (...)Insert
INSERT INTO t SELECT ...Insert from query
UPDATE t SET col = val WHERE ...Update
DELETE FROM t WHERE ...Delete
TRUNCATE TABLE tFast delete all rows
MERGE INTO t USING s ON ...Upsert (SQL Server, Oracle, PostgreSQL 15+)

Upsert

-- PostgreSQL
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;

-- MySQL
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON DUPLICATE KEY UPDATE name = VALUES(name);

UPDATE FROM

-- PostgreSQL
UPDATE orders o
SET total = p.price * o.qty
FROM products p
WHERE o.product_id = p.id;

DDL

StatementPurpose
CREATE TABLENew table
ALTER TABLE ... ADD COLUMNAdd column
ALTER TABLE ... DROP COLUMNRemove column
ALTER TABLE ... ALTER COLUMNChange type/default
CREATE INDEXIndex
CREATE INDEX CONCURRENTLYNon-blocking index (PostgreSQL)
DROP TABLERemove table
DROP TABLE IF EXISTSSafe remove
TRUNCATEFast empty
-- Concurrent index build (PostgreSQL)
CREATE INDEX CONCURRENTLY idx_orders_user ON orders (user_id);

-- Add NOT NULL column with default
ALTER TABLE users ADD COLUMN active boolean DEFAULT true NOT NULL;

Constraints

ConstraintPurpose
PRIMARY KEYUnique + not null
UNIQUEUnique (nullable)
NOT NULLNon-null
CHECK (cond)Validation
FOREIGN KEYReferential integrity
DEFAULT valDefault value
GENERATED ALWAYS AS (...) STOREDComputed column
CREATE TABLE orders (
  id bigserial PRIMARY KEY,
  user_id bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  total numeric(12,2) CHECK (total >= 0),
  created_at timestamptz DEFAULT now()
);

Data Types

TypeUse
integer / bigintWhole numbers
numeric(p,s)Exact decimal
real / double precisionFloating point
text / varchar(n)Strings
booleanTrue/false
dateDate
timestamptzTimestamp with timezone
intervalDuration
json / jsonbJSON (jsonb is binary)
uuidUUID
arrayArrays (PostgreSQL)
enumEnumerated values

String Functions

FunctionPurpose
LENGTH(s)Length
UPPER(s) / LOWER(s)Case
TRIM(s)Strip whitespace
SUBSTRING(s FROM a FOR b)Substring
REPLACE(s, from, to)Replace
CONCAT(a, b) or a || bConcatenate
SPLIT_PART(s, delim, n)Split
REGEXP_REPLACE(s, pattern, repl)Regex replace
LIKE 'pat%'Pattern match (% any, _ one)
ILIKECase-insensitive LIKE (PostgreSQL)

Date/Time Functions

FunctionPurpose
NOW() / CURRENT_TIMESTAMPCurrent time
CURRENT_DATECurrent date
AGE(ts1, ts2)Interval
DATE_TRUNC('month', ts)Truncate
EXTRACT(YEAR FROM ts)Extract part
ts + INTERVAL '1 day'Add interval
ts1 - ts2Interval difference
SELECT DATE_TRUNC('month', created_at) AS month, COUNT(*)
FROM orders
GROUP BY 1
ORDER BY 1;

JSON (PostgreSQL)

OperationSyntax
Access fieldcol->'key' (returns json)
Access field as textcol->>'key'
Access nestedcol->'a'->>'b'
Array elementcol->0
Containscol @> '{"a": 1}'
Key existscol ? 'key'
FilterWHERE col->>'status' = 'active'
SELECT id, data->>'name' AS name
FROM events
WHERE data @> '{"type": "click"}';

Transactions

StatementPurpose
BEGIN / START TRANSACTIONStart
COMMITCommit
ROLLBACKAbort
SAVEPOINT nameSet savepoint
ROLLBACK TO nameRollback to savepoint
RELEASE SAVEPOINT nameRelease savepoint

Isolation Levels

LevelDirty ReadNon-RepeatablePhantom
READ UNCOMMITTEDYesYesYes
READ COMMITTEDNoYesYes
REPEATABLE READNoNoYes*
SERIALIZABLENoNoNo

*PostgreSQL’s REPEATABLE READ also prevents phantoms.

BEGIN ISOLATION LEVEL SERIALIZABLE;
-- ...
COMMIT;

Locking

LockSyntax
Row lockSELECT ... FOR UPDATE
Row lock, skip lockedSELECT ... FOR UPDATE SKIP LOCKED
Row lock, no waitSELECT ... FOR UPDATE NOWAIT
Table lockLOCK TABLE t IN ACCESS EXCLUSIVE MODE
-- Job queue pattern
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;

Indexes

IndexBest For
B-treeEquality, range, sort
HashEquality only
GINArrays, JSONB, full-text
GiSTGeometric, range types
BRINLarge sorted tables
HNSW (pgvector)Vector similarity
CREATE INDEX idx_orders_user_created ON orders (user_id, created_at DESC);
CREATE INDEX idx_events_data ON events USING gin (data);

Execution Plan

CommandPurpose
EXPLAIN queryEstimated plan
EXPLAIN ANALYZE queryActual plan with timing
EXPLAIN (ANALYZE, BUFFERS) queryWith I/O stats
EXPLAIN (VERBOSE) queryWith output columns
NodeMeaning
Seq ScanFull table scan
Index ScanIndex lookup + table fetch
Index Only ScanIndex-only (no table fetch)
Bitmap Heap ScanIndex bitmap + heap fetch
Hash JoinBuild hash table, probe
Nested LoopFor each outer row, scan inner
Merge JoinSorted merge

Common Patterns

Pagination

-- Offset (slow on deep pages)
SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 100;

-- Keyset (fast)
SELECT * FROM orders WHERE id > 1000 ORDER BY id LIMIT 20;

Deduplication

-- Keep the latest row per user
DELETE FROM orders o
USING orders o2
WHERE o.user_id = o2.user_id
  AND o.created_at < o2.created_at;

Running Total

SELECT
  id,
  total,
  SUM(total) OVER (ORDER BY created_at) AS running_total
FROM orders;

Percent of Total

SELECT
  category,
  SUM(amount)::numeric / SUM(SUM(amount)) OVER () AS pct
FROM sales
GROUP BY category;

Find Duplicates

SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

Top N per Group

SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) rn
  FROM products
) t WHERE rn <= 3;

Gotchas

TrapReality
NULL = NULLReturns NULL, not true; use IS NULL
COUNT(col) counts NULLsNo; use COUNT(*) for row count
WHERE can use SELECT aliasesNo; ORDER BY can
NOT IN with NULLReturns no rows; use NOT EXISTS
TRUNCATE is transactionalYes in PostgreSQL, no in MySQL
VARCHAR(n) truncatesNo; errors on overflow (PostgreSQL)
Implicit type castingCan prevent index use
SELECT * in productionFetches unused columns; breaks on schema change
ORDER BY without LIMITSorts full result set
LIKE '%x%'Cannot use B-tree index
OR in WHERECan prevent index use; use UNION
DISTINCT is slowSorts; consider GROUP BY or index
UPDATE without WHEREUpdates all rows
DELETE without WHEREDeletes all rows
COUNT(*) on large tableFull scan; use estimate for large tables

Quick Decision Tables

Which JOIN?

NeedUse
Only matching rowsINNER JOIN
All from one sideLEFT JOIN
All from bothFULL OUTER JOIN
CartesianCROSS JOIN

Which pagination?

NeedUse
Random page accessOFFSET
Deep pages, stable orderKeyset
Cursor APIKeyset

Which index?

QueryIndex
WHERE col = valB-tree
WHERE col @> jsonGIN
ORDER BY colB-tree
Vector similarityHNSW
Large append-onlyBRIN

Which isolation?

NeedLevel
Read-only analyticsREAD COMMITTED
Consistent reads within txREPEATABLE READ
Strict serializabilitySERIALIZABLE

One-Page Flow

QUERY
  FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

WRITE
  BEGIN → INSERT/UPDATE/DELETE → COMMIT (or ROLLBACK)

SCHEMA
  CREATE TABLE → constraints → CREATE INDEX → seed data

OPTIMIZE
  EXPLAIN ANALYZE → identify slow node → add index → re-measure

TRANSACTIONS
  BEGIN → operations → SAVEPOINT → ROLLBACK TO → COMMIT

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!