Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. SQL
SQL8 topics146+ exercises

SQL Syntax Practice

Write SQL that works on the first run.

Short daily reps. Spaced repetition automatically brings back what you keep forgetting.

SQL has no forgiveness. One wrong keyword and the query breaks. One bad JOIN condition and you get a million rows instead of a thousand. One WHERE clause in the wrong place and your LEFT JOIN silently becomes an INNER JOIN.

**SQL execution order**

``` FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → WINDOW → ORDER BY → LIMIT ```

Most SQL bugs come from putting logic in the wrong phase. WHERE filters rows before grouping—you can't use aggregates there. HAVING filters after. Window functions compute after aggregation but before output. Know this order and half your bugs disappear.

**Learning paths**

1. **Day-to-day SQL** (most common): Filtering → Joins → Aggregation → Sorting → Subqueries → CTEs → Window Functions → Data Modification

2. **Interview prep** (what shows up on screeners): Filtering → Joins → Aggregation → Subqueries → Window Functions → Sorting

3. **Backend safety** (production writes): Filtering → Sorting (pagination) → Data Modification (safe writes) → Joins → Aggregation

Popular topics include SQL Filtering Practice: WHERE, LIKE, IN, BETWEEN, NULL, SQL Joins Practice, SQL Aggregation Practice: GROUP BY, HAVING, COUNT, SUM, AVG.

Built for SQL Learners

Great for
  • Analysts and data engineers who query production data.
  • Backend devs who write SQL every week but still trip on syntax.
  • Prepping for data, analytics, or backend interviews.
  • Learning relational databases and want the syntax to stick.
You'll be able to
  • Avoid LEFT JOIN silently turning into INNER JOIN (ON vs WHERE).
  • Write NULL-safe filters without guessing (IS NULL, IS DISTINCT FROM, NOT EXISTS).
  • Paginate without duplicates or skipped rows (tie-breakers + keyset pagination).
  • Use window functions correctly: ROWS vs RANGE frames, LAST_VALUE trap, CTE filtering pattern.
  • Run UPDATE/DELETE safely: preview first, use RETURNING, wrap in transactions.
  • Write GROUP BY and HAVING without confusing which filters where.

Practice by Topic

Pick a concept and train recall with small, focused reps.

25 exercisesGotchas included

SQL Filtering Practice

Stop the NULL comparison bugs and AND/OR precedence mistakes.

Show 3 gotchasHide gotchas
  • = NULL never matches—use IS NULL. SQL uses three-valued logic.
  • NOT IN (subquery) returns 0 rows if subquery contains NULL—use NOT EXISTS.
  • AND binds tighter than OR: a OR b AND c = a OR (b AND c)—always parenthesize.
Learn more
25 exercisesGotchas included

SQL Joins Practice

Stop LEFT JOIN silently becoming INNER JOIN.

Show 3 gotchasHide gotchas
  • WHERE on the right table drops NULL rows—move the filter to ON.
  • Joining on non-unique keys multiplies rows (row explosion).
  • FULL OUTER JOIN not supported in MySQL—use UNION of LEFT + RIGHT.
Learn more
20 exercisesGotchas included

SQL Aggregation Practice

Stop confusing WHERE vs HAVING and getting inflated counts.

Show 3 gotchasHide gotchas
  • WHERE filters rows *before* GROUP BY; HAVING filters *after* aggregation.
  • COUNT(*) counts all rows; COUNT(column) ignores NULLs—they differ with LEFT JOIN.
  • JOINs can multiply rows before aggregation—pre-aggregate or use COUNT(DISTINCT).
Learn more
12 exercisesGotchas included

SQL Sorting & Pagination Practice

Stop getting duplicates and skipped rows in pagination.

Show 3 gotchasHide gotchas
  • LIMIT without deterministic ORDER BY gives unpredictable subsets—add a unique tie-breaker.
  • OFFSET is slow for deep pages (scans + discards)—use keyset/cursor pagination.
  • NULL ordering varies by database—use NULLS LAST or the portable (col IS NULL) trick.
Learn more
15 exercisesGotchas included

SQL Subqueries Practice

Know when IN vs EXISTS matters and avoid the NOT IN trap.

Show 3 gotchasHide gotchas
  • NOT IN + NULL = 0 rows. The NULL comparison makes everything UNKNOWN.
  • Correlated subqueries can run per-row—rewrite as JOIN + GROUP BY when possible.
  • LATERAL lets subqueries in FROM reference outer columns (top-1 per row pattern).
Learn more
20 exercisesGotchas included

SQL Window Functions Practice

Master frames, ranking, and the CTE filtering pattern.

Show 3 gotchasHide gotchas
  • Can't filter window functions in WHERE—wrap in CTE, then filter (or use QUALIFY).
  • Default frame is RANGE (ties grouped)—use ROWS for deterministic running totals.
  • LAST_VALUE returns current row with default frame—extend to UNBOUNDED FOLLOWING.
Learn more
11 exercisesGotchas included

SQL CTEs Practice

Write readable multi-step queries and recursive traversals.

Show 3 gotchasHide gotchas
  • CTEs are materialized differently by database—Postgres 12+ inlines unless MATERIALIZED.
  • WITH RECURSIVE needs a base case and a recursive case joined by UNION ALL.
  • Circular recursion without a stopping condition = infinite loop.
Learn more
18 exercisesGotchas included

SQL Data Modification Practice

Write UPDATE/DELETE without destroying production data.

Show 3 gotchasHide gotchas
  • UPDATE/DELETE without WHERE affects ALL rows—preview with SELECT first.
  • Always use RETURNING to verify what changed before COMMIT.
  • ON CONFLICT requires a unique constraint on the conflict target.
Learn more

SQL Cheat Sheets

Copy-ready syntax references for quick lookup

SELECT QueriesJoinsAggregationWindow FunctionsCTEsSubqueries

What You'll Practice

Real SQL patterns you'll type from memory

Anti-join (rows in A not in B)
SELECT c.*
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.customer_id IS NULL;
Top N per group (ROW_NUMBER)
WITH ranked AS (
  SELECT *,
    ROW_NUMBER() OVER (
      PARTITION BY customer_id
      ORDER BY amount DESC, id DESC
    ) AS rn
  FROM orders
)
SELECT * FROM ranked WHERE rn <= 3;
Keyset pagination (Postgres/MySQL 5.7+)
SELECT *
FROM users
WHERE (created_at, id) < (:cursor_ts, :cursor_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;
Running total (explicit ROWS frame)
SELECT date, amount,
  SUM(amount) OVER (
    ORDER BY date, id
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total
FROM transactions;
Safe UPDATE (Postgres RETURNING)
BEGIN;
SELECT * FROM users WHERE status = 'inactive';
UPDATE users SET deleted_at = NOW()
WHERE status = 'inactive'
RETURNING id, email;
COMMIT;
Conditional aggregate (SUM CASE)
SELECT category,
  SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
  COUNT(*) AS total
FROM orders
GROUP BY category;
Half-open date range (timestamp-safe)
SELECT * FROM orders
WHERE created_at >= '2024-01-01'
  AND created_at < '2024-02-01';
UPSERT (Postgres ON CONFLICT)
INSERT INTO stats (user_id, date, count)
VALUES (1, '2024-01-15', 1)
ON CONFLICT (user_id, date)
DO UPDATE SET count = stats.count + EXCLUDED.count;
Default for NULL (COALESCE)
SELECT name,
  COALESCE(nickname, name) AS display_name,
  COALESCE(discount, 0) AS discount
FROM users;

Sample Exercises

Example 1Difficulty: 1/5

Select all orders from the orders table where the status equals 'shipped'.

SELECT * FROM orders WHERE status = 'shipped'
Example 2Difficulty: 2/5

Write a query to join the orders table with the users table on user_id, selecting all columns.

SELECT * FROM orders INNER JOIN users ON orders.user_id = users.id

Done Googling SQL syntax?

Ten minutes a day. No setup. The syntax sticks.

Why this works

You lose SQL syntax fast if you don't use it regularly. The algorithm re-tests each pattern right before you'd forget it. A few rounds of that and the queries come out clean on the first try.

What to expect

Week 1: The algorithm exposes your weak spots—maybe NULL handling, maybe window function frames, maybe pagination. You'll see which patterns you blank on.

Week 2-4: Intervals stretch. Things you got right move to 2-day, 4-day, then weekly reviews. The gotchas that burned you become obvious.

Month 2+: SQL syntax is muscle memory. Reviews take 10 minutes. You're maintaining, not catching up.

FAQ

Is this Postgres-first or truly portable?

Standard SQL core, with dialect notes where syntax differs. You learn the portable patterns first, then we note where Postgres uses ON CONFLICT while MySQL uses ON DUPLICATE KEY, or where FULL OUTER JOIN needs a UNION workaround in MySQL.

Do you cover keyset pagination and ORDER BY tie-breakers?

Yes. The sorting topic covers both OFFSET pagination (with deterministic tie-breakers) and keyset/cursor pagination for performance. If pagination bugs have bitten you before, this fixes that.

I'm a beginner—what should I learn first?

Start with Filtering (WHERE clause, NULL handling), then Joins, then Aggregation (GROUP BY/HAVING). That's the foundation for everything else. Window functions and CTEs come after you're solid on the basics.

Will this help with interviews?

The exercises match what shows up in technical screenings: JOINs, aggregation, window functions (ROW_NUMBER for top-N), subqueries. The interview path focuses on these patterns.

How are the window function exercises different from tutorials?

Most tutorials skip the gotchas: ROWS vs RANGE default frames, LAST_VALUE returning current row, ORDER BY in OVER not sorting output, ROW_NUMBER needing tie-breakers. We drill the edge cases that bite you in production.

Sources
PostgreSQL: SELECT QueryPostgreSQL: Window FunctionsPostgreSQL: INSERT ON CONFLICTUse The Index, Luke: PaginationMySQL: JOIN Clause

Try it. It takes two minutes.

Free daily exercises with spaced repetition. No credit card required.

Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.