Các loại JOIN (ví dụ users ↔ orders):
- INNER JOIN: chỉ hàng khớp ở cả hai bảng — user có ít nhất một đơn.
- LEFT JOIN: mọi hàng bảng trái + phần khớp bảng phải; không khớp → cột phải là NULL — mọi user, kể cả chưa có đơn.
- RIGHT JOIN: đối xứng ngược lại (thực tế ít dùng — viết lại thành LEFT cho dễ đọc).
- FULL OUTER JOIN: giữ cả hàng không khớp ở hai phía.
- CROSS JOIN: tích Descartes — mọi cặp kết hợp.
GROUP BY + HAVING — đề tay hay gặp: "khách nào có tổng chi tiêu trên 1 triệu":
SELECT u.id, u.name, SUM(o.amount) AS total_spent
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid' -- filters ROWS, before grouping
GROUP BY u.id, u.name
HAVING SUM(o.amount) > 1000000 -- filters GROUPS, after aggregation
ORDER BY total_spent DESC;WHERE vs HAVING: WHERE lọc từng hàng trước khi gom nhóm nên không dùng được hàm tổng hợp; HAVING lọc nhóm sau khi GROUP BY nên mới dùng được SUM/COUNT/AVG. Mọi cột trong SELECT không nằm trong hàm tổng hợp phải xuất hiện ở GROUP BY.
Bẫy hay gặp: đếm "user chưa có đơn" bằng COUNT(*) sau LEFT JOIN (đếm cả hàng NULL — phải dùng COUNT(o.id)); đặt điều kiện của bảng phải vào WHERE khiến LEFT JOIN thành INNER (điều kiện đó phải nằm trong ON).