SQL Fundamentals: The Cheat Sheet I Wish I Had on Day One
Everything you'll actually use — SELECT, JOINs, WHERE on dates, IS NULL, LIKE, LIMIT — with examples you can copy and the gotchas that waste an afternoon.

SQL is the language databases speak. If you're reading this you've probably already accepted that you need to learn it — or at least learn enough to stop pinging the data team every time you want to know last quarter's numbers. Good news: the 80/20 of SQL is genuinely small. Maybe twelve patterns total cover most of what you'll write in a year.
This page is the cheat sheet I wish someone had handed me on day one. Real syntax, real examples, the formatting that's easy to mess up. At the end I'll point at the open-source online editors that beat installing anything locally — and explain when typing the SQL is genuinely the wrong move.
What SQL means and why it stuck
SQL stands for Structured Query Language. It's pronounced either 'S-Q-L' or 'sequel' depending on which decade your mentor learned it in — both are correct, neither argument is interesting.
It's been the standard language for talking to relational databases since the 1970s and it's still the standard today, fifty years later. That's not because nobody's tried to replace it. It's because the relational model — tables with rows and columns, joins between them — is a really good fit for how business data is shaped, and SQL is a really good fit for asking questions of that model. Newer query languages exist; none of them have stuck the way SQL has.
The SELECT skeleton
Almost every query you write follows this skeleton. The full version reads top-to-bottom in the order it executes: FROM picks the table, WHERE filters rows, GROUP BY aggregates, HAVING filters the aggregates, ORDER BY sorts, LIMIT cuts the output.
SELECT customer_id, SUM(amount) AS total
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY customer_id
HAVING SUM(amount) > 100
ORDER BY total DESC
LIMIT 10;customer_id | total
------------|--------
4517 | 8420.50
2031 | 6112.00
8842 | 4980.75
...SQL JOIN types — the four you actually need
JOINs combine rows from two tables based on a related column. There are four types. The mental model: imagine two overlapping circles (Venn diagram) — left table on the left, right table on the right. Each JOIN type is a different shaded region of that diagram.
- 1
INNER JOIN — only rows that exist in both tables
The default. Returns rows where the join key matches in both sides. If a customer has no orders, they don't appear. If an order has no customer (orphan FK), it doesn't appear. The most common JOIN you'll write.
- 2
LEFT JOIN — every row on the left, matched if possible
Keeps every row from the left table. Right-side columns are NULL when there's no match. Use this when you want 'every customer, plus their orders if any' — orphans on the left side stay; orphans on the right disappear.
- 3
RIGHT JOIN — mirror of LEFT JOIN
Same idea, opposite direction. Almost nobody uses RIGHT JOIN in practice — you flip the table order and use LEFT JOIN instead because it reads more naturally. Worth knowing it exists; not worth optimising for.
- 4
FULL OUTER JOIN — every row from both, matched if possible
All rows from both sides. Where they match, the row is combined. Where they don't, the missing side is NULL. Use this rarely — usually for reconciliation queries ('which records are in A but not B, and vice versa'). Note: MySQL doesn't support FULL OUTER JOIN natively, you simulate it with UNION.
JOIN syntax — the exact pattern
All four JOINs follow the same syntax. The word INNER is optional (the default), and OUTER is optional in LEFT/RIGHT/FULL OUTER. So SELECT … FROM a LEFT JOIN b ON … is the same as SELECT … FROM a LEFT OUTER JOIN b ON ….
SELECT
o.id AS order_id,
o.amount,
c.email,
c.signup_at
FROM orders o
LEFT JOIN customers c
ON c.id = o.customer_id
WHERE o.created_at >= '2026-01-01';order_id | amount | email | signup_at
---------|--------|-----------------|------------
1001 | 49.99 | a@example.com | 2025-08-12
1002 | 125.00 | b@example.com | 2025-11-03
1003 | 15.00 | NULL | NULLWHERE clauses for dates — the source of half my bugs
Dates in SQL are deceptively annoying. Three things bite people repeatedly:
**Date vs timestamp.** A column typed DATE has no time component. A TIMESTAMP includes seconds. WHERE created_at = '2026-05-01' on a TIMESTAMP column matches only midnight exactly — almost never what you want. Use WHERE created_at >= '2026-05-01' AND created_at < '2026-05-02' for 'all of May 1st'.
**Timezones.** TIMESTAMP WITH TIME ZONE stores UTC and converts on read. TIMESTAMP WITHOUT TIME ZONE stores whatever you give it, no conversion. Mixing the two in the same query gives wrong answers. Pick one and stick with it.
**Quote the date string.** WHERE created_at >= 2026-05-01 (no quotes) gets parsed as the math expression 2026 - 5 - 1 = 2020. The query runs, the result is garbage. Always quote: WHERE created_at >= '2026-05-01'.
Date filters that work everywhere
Three patterns that work across PostgreSQL, MySQL, SQL Server, and SQLite. The first is the safest 'last 30 days' filter. The second buckets by month. The third grabs a specific quarter.
-- Last 30 days (database-agnostic)
SELECT * FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days';
-- Group by month
SELECT
DATE_TRUNC('month', created_at) AS month,
COUNT(*) AS orders
FROM orders
GROUP BY 1
ORDER BY 1;
-- Specific quarter (Q1 2026)
SELECT * FROM orders
WHERE created_at >= '2026-01-01'
AND created_at < '2026-04-01';IS NULL — why = NULL never works
NULL means 'unknown'. In SQL, comparing anything to an unknown value is also unknown — including comparing NULL to NULL. That's why WHERE column = NULL never matches any rows: it returns 'unknown', which the WHERE clause treats as false.
The correct syntax is WHERE column IS NULL or WHERE column IS NOT NULL. There's no other way. You can also use COALESCE(column, fallback) when you want to substitute a default. NULL is the most common source of 'why isn't my query returning anything' tickets — when in doubt, check whether the column you're filtering on can be NULL and whether your filter handles it.
IS NULL examples
-- Customers without an email
SELECT * FROM customers WHERE email IS NULL;
-- Customers WITH an email (note IS NOT NULL)
SELECT * FROM customers WHERE email IS NOT NULL;
-- Replace NULL with a default in the output
SELECT id, COALESCE(email, 'no email on file') AS email
FROM customers;LIKE for pattern matching
Use LIKE when you want to match part of a string. Two wildcards: % matches any number of characters (including zero), _ matches exactly one character.
LIKE is case-sensitive in PostgreSQL by default — use ILIKE if you want case-insensitive matching, or wrap both sides in LOWER(). MySQL is case-insensitive by default for LIKE, which is convenient but bites you if you ever migrate to Postgres.
LIKE patterns
-- Emails ending in @gmail.com
SELECT * FROM users WHERE email LIKE '%@gmail.com';
-- Names starting with 'M'
SELECT * FROM customers WHERE name LIKE 'M%';
-- Postcodes that are exactly 5 chars and end in '01'
SELECT * FROM addresses WHERE postcode LIKE '___01';
-- Case-insensitive (PostgreSQL only)
SELECT * FROM users WHERE email ILIKE '%@GMAIL.com';LIMIT and pagination
LIMIT N returns the first N rows of the result. Combined with ORDER BY, it's how you grab 'top 10 by revenue' or 'most recent 100 sign-ups'. Without ORDER BY, the order is undefined and you'll get inconsistent results.
For pagination, OFFSET skips rows. LIMIT 20 OFFSET 40 gives you rows 41-60. This works fine on small datasets but gets slow on large ones because the database still has to scan the offset rows. For pagination over millions of rows, switch to keyset pagination (WHERE id > last_seen_id LIMIT 20) — but that's a different rabbit hole.
Online SQL editors worth using
When you don't want to install anything, these run SQL straight in the browser and are good enough for learning, prototyping, or sharing a query with someone:
- DB Fiddle (db-fiddle.com) — supports PostgreSQL, MySQL, SQLite. Save and share via URL. The one I open most.
- SQLite Online (sqliteonline.com) — drag a CSV into the page and query it. Free. No login.
- DBeaver (community edition) — desktop, but free and supports every major database. The cleanest local client if you'll be writing SQL more than once a week.
- Programiz (programiz.com/sql/online-compiler) — beginner-friendly with a sample database loaded.
- AnalityQa — runs your own data instead of a sample. Type the question in English, see the SQL it generated, edit if you want.
When SQL is overkill
SQL is genuinely the right tool when you're answering the same question repeatedly, when the data is already in a database, when you need joins across multiple tables, or when you want to share the exact query with a teammate so they can verify the logic.
It becomes painful when you're answering a one-off question, when the data is in a CSV someone emailed you, when the question is ad-hoc enough that writing the query takes longer than the answer is worth, or when you genuinely don't know what shape the data is in yet.
This last category — exploration — is what we built AnalityQa for. You drop a CSV or connect a database, you ask the question in English, the system writes the SQL and runs it. You can see the SQL it wrote, edit it if you want, save it as a saved query for later. The point isn't to hide SQL from you; it's to skip the parts that don't need a human.
See it in AnalityQa
You'd type
"Show me a LEFT JOIN of orders and customers, last 90 days, grouped by signup month with revenue and order count"
What you'd get back
Generated this query: SELECT DATE_TRUNC('month', c.signup_at) AS cohort_month, COUNT(o.id) AS orders, SUM(o.amount) AS revenue FROM customers c LEFT JOIN orders o ON o.customer_id = c.id AND o.created_at >= CURRENT_DATE - INTERVAL '90 days' GROUP BY 1 ORDER BY 1. Result returned 6 rows.
What this won't do
This page won't make you a data engineer. It covers the SELECT side of SQL — reading data — and skips most of the schema-design, indexing, and performance-tuning topics that matter when you're building a database, not just querying one. If you need to design a database from scratch, optimize a slow query plan, or set up replication, treat this as a starting point and pair it with a deeper resource (Use The Index, Luke and PostgreSQL's own docs are both excellent).
Note from Alex
I built AnalityQa partly because I was writing the same JOIN every Monday morning. The customers-LEFT JOIN-orders, group by month, last 90 days query. I'd written it dozens of times. The shape was always the same; only the date range and a column name or two differed.
The point of this page isn't to convince you not to learn SQL. It's a useful skill and learning it makes you faster at understanding any data system. The point is to be honest that some queries don't deserve to be re-typed every time.
— Alex, Co-founder, AnalityQa
Frequently asked
Is SQL the same in PostgreSQL, MySQL, and SQL Server?+
The core 80% is the same — SELECT, WHERE, JOIN, GROUP BY all work. The differences show up in date functions (DATE_TRUNC vs DATE_FORMAT), string functions (CONCAT vs ||), pagination (LIMIT vs TOP), and some join syntax (FULL OUTER JOIN doesn't exist in MySQL). The patterns in this page work on all three.
Where can I download a SQL cheat sheet PDF?+
We don't ship a PDF, but the patterns in the code blocks above cover what most cheat sheets do. If you want a single-page reference, the SQLBolt site has good interactive lessons and the W3Schools SQL reference is exhaustive (and ad-heavy, fair warning).
What's the difference between a JOIN and a UNION?+
A JOIN combines rows from two tables side-by-side based on a matching key — wider rows. A UNION stacks the result of two queries on top of each other — more rows, same columns. Use UNION when you want to combine queries that return the same shape (e.g., 'orders from 2025' UNION 'orders from 2026'). Use JOIN when you want to enrich one table's rows with columns from another.
Can I run SQL on a CSV without setting up a database?+
Yes. SQLite Online lets you drop a CSV and query it directly. AnalityQa does the same thing — upload the CSV, ask in English, the SQL runs against an in-memory representation of your file. For files up to a few hundred MB, this is the fastest path.
What does 'sql' actually stand for?+
Structured Query Language. The original name was SEQUEL (Structured English Query Language) but IBM had to rename it because of a trademark conflict. The pronunciation 'sequel' stuck for some people; others say S-Q-L. Both are correct.
Skip the SQL when you can
Drop a CSV or connect a database. Ask the question in English. See the SQL we wrote, edit it if you want, save the dashboard.
Try AnalityQa free100 credits free · No credit card · Cancel anytime