SQL bugs are usually correctness bugs, not crashes. A query returns wrong rows. A migration leaves a constraint missing. A subquery uses an outer column the author didn't realize would be visible. A join produces duplicates because the cardinality assumption was wrong.

The recurring shapes: missing `WHERE` clauses on `UPDATE` and `DELETE` (a destroy-all-data bug), `IN` with a NULL on either side that doesn't match the way the author thought, type coercion between strings and numbers in a `WHERE`, and timezone bugs where the database stores UTC and the application queries naive local timestamps.

Defenses: every destructive statement should have a `WHERE` clause checked twice or wrapped in a transaction with `ROLLBACK` on the wrong row count. Every join should be reasoned about in terms of cardinality. Every column should declare a default and a NOT NULL where the model demands it.

Common pitfalls

Missing WHERE on destructive statements

`UPDATE` or `DELETE` without `WHERE` truncates the table. Rehearse destructive queries in a transaction first.

NULL semantics

`x = NULL` is never true. `x IN (NULL)` is never true. `x = NULL OR x IS NULL` is what most authors meant.

Cardinality surprises in joins

A `LEFT JOIN` to a table where rows can match more than once turns 100 rows into 200. Group by or distinct fixes the symptom; the model usually has a deeper issue.