Họ đang đo gì
Bạn có thật sự viết SQL hay chỉ gọi ORM. Người viết SQL thật đều từng dính bẫy WHERE-sau-LEFT-JOIN ít nhất một lần.
Trả lời ngắn~30 giây
INNER JOIN chỉ giữ những hàng khớp ở cả hai bảng. LEFT JOIN giữ toàn bộ bảng trái, cột bên phải thành NULL khi không khớp. FULL OUTER giữ cả hai bên. Điều đáng nói hơn: nếu bạn lọc cột bên phải trong WHERE, hàng NULL bị loại và LEFT JOIN im lặng thoái hoá thành INNER JOIN — điều kiện đó phải nằm trong ON.
Giải thích sâu
Cách hình dung có ích hơn sơ đồ Venn: JOIN sinh ra một tập hàng, rồi WHERE lọc tập đó. LEFT JOIN nói “giữ hàng bên trái kể cả khi không có hàng bên phải”, và khi không có, mọi cột bên phải là NULL. Đến lượt WHERE, một điều kiện như o.status = 'paid' đánh giá NULL thành “không đúng” nên chính những hàng bạn muốn giữ lại bị loại.
Vì vậy quy tắc thực hành là: điều kiện thuộc về bảng bên ngoài (bảng có thể thiếu) thì đặt trong ON; điều kiện lọc kết quả cuối thì đặt trong WHERE. Với INNER JOIN thì hai chỗ cho cùng kết quả, nên nhiều người quen tay đặt đâu cũng được — rồi đổi sang LEFT JOIN và bug xuất hiện sáu tháng sau, ở báo cáo đếm thiếu khách hàng.
MySQL không hỗ trợ FULL OUTER JOIN; cách thay thế là LEFT JOIN UNION RIGHT JOIN. Đây là chi tiết nhỏ nhưng nói ra được thì thấy ngay bạn từng làm với engine thật chứ không chỉ đọc tài liệu chuẩn SQL.
Cùng một ý định, hai kết quả khác nhau
-- Đếm ĐÚNG: khách hàng nào cũng xuất hiện, kể cả khách chưa mua gì
SELECT c.id, count(o.id) AS paid_orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid'
GROUP BY c.id;
-- Đếm SAI: WHERE loại hết khách chưa có đơn 'paid'
SELECT c.id, count(o.id) AS paid_orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'paid' -- <- LEFT JOIN vừa thành INNER JOIN
GROUP BY c.id;Truy vấn thứ hai không báo lỗi, không cảnh báo. Nó chỉ trả về ít khách hàng hơn.
Câu hỏi tiếp theo họ sẽ hỏi
?Vậy WHERE o.id IS NULL sau LEFT JOIN để làm gì?
Đó là anti-join: giữ đúng những hàng bên trái KHÔNG có hàng khớp. Dùng để tìm “khách chưa từng đặt đơn” mà không cần subquery NOT EXISTS. Đây là trường hợp duy nhất mà lọc cột bên phải trong WHERE là cố ý.
?CROSS JOIN dùng khi nào?
Khi bạn thật sự cần tích Descartes: sinh lưới ngày × sản phẩm để báo cáo có đủ ô kể cả ngày không bán được gì. Ngoài trường hợp đó, một CROSS JOIN xuất hiện trong query thường là JOIN bị quên điều kiện.
Trả lời thế này là mất điểm
- Vẽ sơ đồ Venn rồi dừng. Sơ đồ Venn mô tả tập hợp, còn JOIN sinh ra hàng — nó không giải thích được vì sao một hàng bên trái có thể xuất hiện ba lần.
- Nói “LEFT JOIN chậm hơn INNER JOIN”. Chi phí phụ thuộc vào index và ước lượng của planner, không phải vào từ khoá.
Trả lời thế này là ghi điểm
- Nhắc rằng JOIN có thể NHÂN số hàng, nên
count(*)sau join gần như luôn sai — phảicount(DISTINCT …)hoặc gộp trước rồi mới join.