SQL#

Most examples target PostgreSQL; differences noted where MySQL or SQLite diverge.

Top N per Group#

-- Latest order per customer
SELECT *
FROM (
  SELECT o.*, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at DESC) AS rn
  FROM   orders o
) x
WHERE rn = 1;

Running Total#

SELECT id,
       amount,
       SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at
                         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
         AS running_total
FROM orders;

Lead / Lag#

SELECT id,
       amount,
       LAG(amount)  OVER (PARTITION BY user_id ORDER BY created_at) AS prev_amount,
       LEAD(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS next_amount,
       amount - LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS delta
FROM orders;

Percentiles#

SELECT percentile_cont(0.50) WITHIN GROUP (ORDER BY duration_ms) AS p50,
       percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95,
       percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms) AS p99
FROM   requests
WHERE  created_at >= now() - interval '1 hour';

Upsert#

PostgreSQL:

INSERT INTO users (id, email, name, last_seen)
VALUES (1, 'operator@example.com', 'Ada', now())
ON CONFLICT (id) DO UPDATE
  SET email     = EXCLUDED.email,
      name      = EXCLUDED.name,
      last_seen = EXCLUDED.last_seen;

MySQL:

INSERT INTO users (id, email, name, last_seen)
VALUES (1, 'operator@example.com', 'Ada', NOW())
ON DUPLICATE KEY UPDATE
  email     = VALUES(email),
  name      = VALUES(name),
  last_seen = VALUES(last_seen);

SQLite:

INSERT INTO users (id, email, name, last_seen)
VALUES (1, 'operator@example.com', 'Ada', strftime('%s','now'))
ON CONFLICT(id) DO UPDATE SET
  email     = excluded.email,
  name      = excluded.name,
  last_seen = excluded.last_seen;

RETURNING (Postgres)#

INSERT INTO users (email) VALUES ('operator@example.com')
RETURNING id, created_at;

UPDATE users SET last_seen = now() WHERE id = 1
RETURNING last_seen;

DELETE FROM sessions WHERE expires_at < now()
RETURNING id;

JSON Operations#

PostgreSQL JSON / JSONB operators, one example per operator.

Top-level field access with ->>.

SELECT data->>'name' AS name FROM events;

Nested-path access with #>> and a path array.

SELECT data#>>'{user,email}' FROM events;

Containment filter with @>.

SELECT * FROM events WHERE data @> '{"type": "click"}';

Aggregate rows into a JSON array.

SELECT jsonb_agg(jsonb_build_object('id', id, 'name', name))
FROM users;

Spread a JSON array into rows with jsonb_array_elements.

SELECT id, x->>'name' AS name, (x->>'age')::int AS age
FROM events, LATERAL jsonb_array_elements(data->'users') x;

Date Bucketing#

Bucket rows into one row per day. Days with no rows are absent from the result.

SELECT date_trunc('day', created_at) AS day,
       COUNT(*) AS orders,
       SUM(total) AS revenue
FROM   orders
WHERE  created_at >= now() - interval '30 days'
GROUP  BY day
ORDER  BY day;

Use generate_series and a LEFT JOIN when the result must include zero-rows for empty days.

SELECT g.day,
       COALESCE(o.orders, 0) AS orders
FROM   generate_series(date_trunc('day', now() - interval '29 days'),
                       date_trunc('day', now()),
                       interval '1 day') AS g(day)
LEFT JOIN (
  SELECT date_trunc('day', created_at) AS day, COUNT(*) AS orders
  FROM   orders GROUP BY 1
) o ON o.day = g.day
ORDER BY g.day;

Pagination#

Offset-based (simple, slow at high offsets):

SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;

Keyset (recommended):

SELECT * FROM posts
WHERE id > :last_seen_id
ORDER BY id
LIMIT 20;

Recursive CTE#

WITH RECURSIVE org AS (
  SELECT id, manager_id, 1 AS depth
  FROM   employees
  WHERE  manager_id IS NULL

  UNION ALL

  SELECT e.id, e.manager_id, o.depth + 1
  FROM   employees e
  JOIN   org o ON e.manager_id = o.id
)
SELECT * FROM org;

Lateral Join#

-- Most recent 3 orders per customer
SELECT c.id, o.*
FROM   customers c
LEFT JOIN LATERAL (
  SELECT * FROM orders
  WHERE  customer_id = c.id
  ORDER  BY created_at DESC
  LIMIT  3
) o ON true;

EXPLAIN#

Plain EXPLAIN ANALYZE runs the query and reports the actual times.

EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 1 ORDER BY created_at DESC LIMIT 10;

Add BUFFERS to surface cache hits and VERBOSE for column detail.

EXPLAIN (ANALYZE, BUFFERS, VERBOSE) ...;

Index Recipes#

Compound index over the columns of a common query.

CREATE INDEX idx_orders_customer_recent
  ON orders (customer_id, created_at DESC);

Partial index over an active subset of rows.

CREATE INDEX idx_active_users ON users (created_at)
WHERE active;

Functional index over the result of an expression.

CREATE UNIQUE INDEX idx_users_email_lower
  ON users (lower(email));

Concurrent build avoids taking a table lock during the build (Postgres).

CREATE INDEX CONCURRENTLY idx_x ON t (col);

Common Constraints#

A non-negative CHECK constraint.

ALTER TABLE orders
  ADD CONSTRAINT orders_total_nonneg CHECK (total >= 0);

An enumerated-status CHECK constraint.

ALTER TABLE orders
  ADD CONSTRAINT orders_status_valid
  CHECK (status IN ('pending','paid','shipped','cancelled'));

A functional UNIQUE constraint.

ALTER TABLE orders
  ADD CONSTRAINT orders_email_unique UNIQUE (lower(email));

Transactions#

BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- ROLLBACK on error

-- Savepoints
BEGIN;
  INSERT INTO ...;
  SAVEPOINT s1;
  UPDATE ...;          -- if this fails:
  ROLLBACK TO s1;      -- ... we keep the INSERT
COMMIT;