A quick reference for SQL query syntax: clause order, joins, common functions, and where MySQL, PostgreSQL, SQLite, and T-SQL disagree. Paste any query into the SQL Formatter to have it beautified for the dialect you pick.

Clause order: written vs executed

SQL is written in one order but evaluated in a different one — this is the single most common source of confusion for anyone new to the language, including why a column alias defined in SELECT cannot be used in that same query's WHERE clause.

Written orderLogical execution order
1. SELECT4. SELECT
2. FROM1. FROM
3. WHERE2. WHERE
4. GROUP BY3. GROUP BY / HAVING
5. HAVING5. ORDER BY
6. ORDER BY6. LIMIT / OFFSET
7. LIMIT

Because FROM and WHERE run before SELECT even exists, a WHERE clause cannot reference a column alias created in SELECT — that alias does not exist yet at the point WHERE is evaluated. ORDER BY, which runs last, can reference SELECT aliases without issue.

Joins

JoinReturns
INNER JOINOnly rows with a match in both tables
LEFT JOINAll rows from the left table, matched rows from the right, NULL where no match
RIGHT JOINAll rows from the right table, matched rows from the left, NULL where no match
FULL OUTER JOINAll rows from both tables, NULL on whichever side has no match
CROSS JOINEvery row from the left paired with every row from the right (cartesian product)

SQLite and MySQL (before 8.0.14) do not support FULL OUTER JOIN directly — it is commonly emulated with a LEFT JOIN and a RIGHT JOIN combined with UNION.

Filtering

OperatorMeaning
=, <> / !=Equal, not equal
BETWEEN a AND bInclusive range
IN (a, b, c)Matches any value in the list
LIKE '%abc%'Pattern match — % any sequence, _ any single character
IS NULL / IS NOT NULLNULL check — never use = NULL, it never matches

NULL is not a value that can be compared with =: it represents "unknown," and any comparison against unknown is itself unknown, not true. WHERE col = NULL silently matches zero rows instead of raising an error.

Aggregates and GROUP BY

FunctionReturns
COUNT(*)Number of rows
SUM(col)Sum of a numeric column
AVG(col)Average of a numeric column
MIN(col) / MAX(col)Smallest / largest value

Every non-aggregated column in SELECT must appear in GROUP BY. Filter on an aggregate with HAVING, not WHEREWHERE runs before grouping happens, so it cannot see the aggregated result yet.

Dialect differences

FeatureMySQLPostgreSQLSQLiteT-SQL
Limit rowsLIMIT nLIMIT nLIMIT nTOP n
Quote identifiers`name`"name""name"[name]
String concatCONCAT(a,b)a || ba || ba + b
Auto-increment PKAUTO_INCREMENTSERIAL / GENERATED ALWAYS AS IDENTITYAUTOINCREMENTIDENTITY(1,1)

Standard SQL (ISO/ANSI) uses FETCH FIRST n ROWS ONLY for row limiting, which LIMIT and TOP both predate as vendor extensions — PostgreSQL and modern SQL Server both support the standard form too, alongside their own.

Frequently asked questions

SQL is written SELECT-first but executed FROM-first: FROM and WHERE both run before SELECT creates any aliases, so the alias does not exist yet at the point WHERE is evaluated.

ORDER BY runs last, after SELECT, which is why it can reference SELECT aliases without any issue.