Bài 1 dừng lại ở việc đọc dữ liệu từ
1 bảng duy nhất. Nhưng dữ liệu thật gần như luôn nằm rải rác trên nhiều bảng có liên
hệ với nhau — đúng như 4 bảng customers/products/orders/
order_items của TechMart. JOIN là công cụ ghép các bảng đó lại theo 1 điều
kiện khớp nào đó, và nắm chắc 5 loại JOIN — cùng cách chọn đúng loại cho đúng bài
toán — là kỹ năng SQL quan trọng nhất sau SELECT/WHERE. Bài này đi qua từng
loại bằng nhiều tình huống kinh doanh thật khác nhau, không dừng ở 1 ví dụ minh hoạ duy nhất, và có 1
Venn diagram tương tác chạy trực tiếp trên engine SQLite thật để bạn nhìn thấy chính xác JOIN nào giữ
lại vùng dữ liệu nào.
1. Khoá Chính & Khoá Ngoại: Nền Tảng Của JOIN
Một khoá chính (primary key) là cột (hoặc tổ hợp cột) định danh duy nhất 1 hàng trong
bảng — TechMart dùng customer_id, product_id, order_id,
order_item_id làm khoá chính cho 4 bảng tương ứng. Một
khoá ngoại (foreign key) là cột ở 1 bảng tham chiếu tới khoá chính của bảng khác —
orders.customer_id trỏ tới customers.customer_id,
order_items.order_id trỏ tới orders.order_id,
order_items.product_id trỏ tới products.product_id. JOIN chính
là thao tác nối 2 bảng dựa trên đúng mối quan hệ khoá chính↔khoá ngoại này.
sql-techmart-seed.sql ở tab "SQL" bên dưới, bạn sẽ thấy
orders.customer_id không hề khai báo
REFERENCES customers(customer_id) — SQLite mặc định
không kiểm tra ràng buộc khoá ngoại trừ khi bật rõ ràng bằng
PRAGMA foreign_keys = ON; (và phải bật lại mỗi kết nối mới, không phải cấu hình toàn
cục). Ngược lại, bản seed PostgreSQL bạn dùng trong lab Docker ở
Bài 2 khai báo REFERENCES tường minh và
luôn ép buộc — cố INSERT 1 đơn hàng với customer_id
không tồn tại sẽ bị PostgreSQL từ chối ngay, trong khi SQLite mặc định cho qua lặng lẽ. Đây là 1
khác biệt dialect quan trọng khác cần nhớ ngoài type affinity đã học ở Bài 1.
2. INNER JOIN: Chỉ Giữ Lại Cặp Khớp Nhau
INNER JOIN (hoặc viết tắt JOIN) chỉ trả về những hàng có
khớp ở cả 2 bảng theo điều kiện ON — hàng không tìm được cặp tương ứng ở
bảng còn lại sẽ bị loại hoàn toàn khỏi kết quả. Dưới đây là 4 tình huống kinh doanh thật minh hoạ
INNER JOIN ở các quy mô khác nhau (2 bảng, 3 bảng, kết hợp GROUP BY, lọc
theo tên):
Tình huống 1 — Danh sách đơn hàng kèm tên khách: ghép 2 bảng đơn giản nhất, chỉ muốn xem đơn hàng của khách đã đăng ký tài khoản (bỏ qua đơn khách vãng lai).
SELECT o.order_id, c.full_name, o.order_date, o.total_amount
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
ORDER BY o.order_date;
Vì orders có 4 đơn với customer_id IS NULL (đơn khách vãng lai, xem lại Bài
1), INNER JOIN loại thẳng 4 đơn này khỏi kết quả — chỉ còn 24/28 đơn. Đây chính là hành
vi cốt lõi của INNER JOIN: không khớp = không xuất hiện, dù hàng đó hoàn
toàn hợp lệ ở bảng gốc.
Tình huống 2 — Hoá đơn chi tiết (invoice): nối 3 bảng liên tiếp để
hiển thị từng dòng sản phẩm cụ thể trong 1 đơn hàng, kèm tên sản phẩm thay vì chỉ
product_id.
SELECT o.order_id, p.product_name, oi.quantity, oi.unit_price,
oi.quantity * oi.unit_price AS line_total
FROM orders o
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
WHERE o.order_id = 12
ORDER BY p.product_name;
Nối 3 bảng chỉ là 2 lần INNER JOIN liên tiếp — không có cú pháp đặc biệt
nào khác. SQLite xử lý từng cặp JOIN theo thứ tự viết (dù optimizer có thể chọn thứ tự
thực thi khác bên trong — xem Bài 9), miễn kết quả cuối
cùng đúng theo logic quan hệ.
Tình huống 3 — Doanh thu theo danh mục sản phẩm: kết hợp INNER JOIN với
GROUP BY để trả lời câu hỏi kinh doanh "danh mục nào bán chạy nhất".
SELECT p.category, SUM(oi.quantity * oi.unit_price) AS revenue
FROM order_items oi
INNER JOIN products p ON oi.product_id = p.product_id
GROUP BY p.category
ORDER BY revenue DESC;
Đây là khuôn mẫu cực kỳ phổ biến trong báo cáo phân tích: JOIN để lấy thuộc tính mô tả
(danh mục) rồi GROUP BY/SUM để tổng hợp.
Bài 4 sẽ đào sâu GROUP BY/HAVING, nhưng
việc JOIN trước khi tổng hợp là kỹ năng của bài này.
Tình huống 4 — Khách đã từng mua 1 sản phẩm cụ thể: lọc theo tên sản phẩm ở bảng thứ 3 để tìm khách hàng liên quan, phục vụ chiến dịch marketing "ai đã mua Laptop Stand".
SELECT DISTINCT c.full_name, c.email
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
WHERE p.product_name = 'Laptop Stand';
DISTINCT ở đây cần thiết vì 1 khách có thể mua cùng 1 sản phẩm nhiều lần qua nhiều đơn
khác nhau — không có nó, tên khách sẽ lặp lại nhiều lần trong kết quả.
3. LEFT JOIN (LEFT OUTER JOIN): Giữ Toàn Bộ Bên Trái
LEFT JOIN giữ lại toàn bộ hàng của bảng bên trái (bảng viết ngay sau
FROM), bất kể có khớp được với bảng bên phải hay không — nếu không khớp, các cột của bảng
phải sẽ là NULL. Đây là loại JOIN được dùng nhiều thứ nhì sau INNER JOIN,
đặc biệt khi câu hỏi kinh doanh có dạng "toàn bộ X, kèm thông tin Y nếu có".
Tình huống 1 — Toàn bộ đơn hàng, kể cả đơn khách vãng lai: đảo ngược Tình huống 1 của
INNER JOIN — lần này không được bỏ sót đơn nào.
SELECT o.order_id, c.full_name, o.order_date, o.total_amount
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
ORDER BY o.order_date;
Kết quả có đủ 28/28 đơn hàng — 4 đơn khách vãng lai vẫn xuất hiện, chỉ là cột
full_name hiện NULL thay vì bị loại hẳn như INNER JOIN. Đây
chính xác là cơ chế đứng sau truy vấn WHERE customer_id IS NULL ở Bài 1 — giờ bạn thấy
toàn cảnh cả 24 đơn khớp lẫn 4 đơn không khớp trong cùng 1 kết quả.
Tình huống 2 — Mọi khách hàng kèm tổng chi tiêu: kết hợp LEFT JOIN +
GROUP BY để không bỏ sót khách chưa từng mua hàng (nếu có) trong báo cáo.
SELECT c.full_name, COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.full_name
ORDER BY total_spent DESC;
Nếu dùng INNER JOIN ở đây, bất kỳ khách nào chưa từng đặt đơn nào sẽ
biến mất khỏi báo cáo hoàn toàn thay vì hiện với tổng chi tiêu 0 — sai lệch nghiêm
trọng cho mục đích "toàn bộ khách hàng". COALESCE(..., 0) thay NULL (do
không có đơn nào để SUM) bằng số 0 để báo cáo dễ đọc hơn.
Tình huống 3 — Sản phẩm bán chạy vs sản phẩm ế: mọi sản phẩm kèm số lượng đã bán, kể cả sản phẩm chưa từng có ai mua.
SELECT p.product_name, p.category, COALESCE(SUM(oi.quantity), 0) AS units_sold
FROM products p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.product_name, p.category
ORDER BY units_sold ASC;
Chạy thử trong sân chơi bên dưới: với dataset TechMart hiện tại, mọi sản phẩm đều đã bán được ít nhất
1 lần nên không có hàng nào units_sold = 0 — nhưng cấu trúc truy vấn này vẫn
đúng và cần thiết, vì dữ liệu thật sẽ liên tục thay đổi (thêm sản phẩm mới chưa bán
được ngày nào là chuyện bình thường). Muốn tự kiểm chứng, gõ thêm 1 sản phẩm mới vào bảng
products ngay trong workbench (INSERT INTO products (product_id, product_name, category, unit_price, stock_quantity) VALUES (15,
'Test Sản Phẩm Mới', 'Accessories', 9.99, 10);) rồi chạy lại — sản phẩm mới sẽ hiện với units_sold = 0 đúng như kỳ vọng.
WHERE vô tình biến LEFT JOIN thành INNER
JOIN
-- SAI: ý định "toàn bộ đơn hàng, ưu tiên đơn delivered của khách có tài khoản"
-- nhưng thực chất lại loại hết đơn khách vãng lai!
SELECT o.order_id, c.full_name, o.status
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
WHERE c.country = 'Vietnam';
Với đơn khách vãng lai, c.country là NULL (vì không khớp được hàng
customers nào) — và theo logic 3 trị đã học ở Bài 1, NULL = 'Vietnam' luôn
cho UNKNOWN, bị WHERE loại bỏ. Kết quả: toàn bộ đơn khách vãng lai biến
mất, dù LEFT JOIN đã cố tình giữ chúng lại ở bước JOIN. Muốn lọc điều kiện
theo bảng phải mà vẫn giữ hàng không khớp, đưa điều kiện đó vào
mệnh đề ON thay vì WHERE:
LEFT JOIN customers c ON o.customer_id = c.customer_id AND c.country = 'Vietnam'.
4. RIGHT JOIN & FULL OUTER JOIN: Khi Cả 2 Hướng Đều Quan Trọng
RIGHT JOIN làm ngược lại LEFT JOIN: giữ toàn bộ bảng bên phải, điền
NULL cho bên trái không khớp. FULL OUTER JOIN (hay FULL JOIN)
giữ cả 2 bên — vừa không bỏ sót hàng trái không khớp, vừa không bỏ sót hàng phải
không khớp.
Tình huống 1 — RIGHT JOIN là LEFT JOIN đổi hướng nhìn: viết lại đúng Tình huống 1 của
LEFT JOIN ở trên, chỉ đổi thứ tự bảng và loại JOIN.
SELECT o.order_id, c.full_name, o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id
ORDER BY o.order_date;
Chạy thử: kết quả giống hệt ví dụ LEFT JOIN orders → customers ở mục 3,
chỉ khác thứ tự cột trả về (do thứ tự bảng trong SELECT/FROM đổi chỗ). Đây
là lý do trong thực tế, lập trình viên hiếm khi dùng RIGHT JOIN — họ quen viết lại thành
LEFT JOIN bằng cách đổi chỗ 2 bảng, vì tư duy "giữ bảng chính ở FROM, JOIN thêm bảng phụ
vào" dễ đọc hơn "giữ bảng phụ ở phía RIGHT". Một số hệ quản trị cũ hơn (SQLite trước phiên bản 3.39.0,
phát hành tháng 6/2022) thậm chí không hỗ trợ RIGHT JOIN hay
FULL OUTER JOIN native — mọi engine SQLite trước đó buộc phải viết lại thành
LEFT JOIN.
Tình huống 2 — FULL OUTER JOIN: không bỏ sót cả 2 hướng bất thường: ghép khách hàng với đơn hàng theo cách không bỏ sót bất kỳ bất thường nào ở cả 2 phía.
SELECT c.full_name, o.order_id, o.order_date
FROM customers c
FULL OUTER JOIN orders o ON c.customer_id = o.customer_id
ORDER BY c.customer_id;
Chạy thử trong sân chơi: bạn sẽ thấy 4 hàng với full_name IS NULL (4 đơn khách vãng lai —
phía "orders không khớp customers"), nhưng không có hàng nào với
order_id IS NULL (phía "customers không khớp orders") — vì trong dataset TechMart hiện
tại, cả 12 khách hàng đăng ký đều đã đặt ít nhất 1 đơn. Đây là điểm khác biệt cốt lõi giữa
FULL OUTER JOIN và LEFT/RIGHT JOIN: nó sẵn sàng hiện
cả 2 loại bất thường cùng lúc, dù dataset hiện tại chỉ đang thể hiện 1 loại. Muốn tự
mắt thấy loại còn lại, thử thêm 1 khách hàng mới chưa từng mua gì ngay trong workbench (INSERT INTO customers (customer_id, full_name, email, country, signup_date, is_active) VALUES (13,
'Khách Test Chưa Mua', '[email protected]', 'Vietnam', '2026-04-01', 1);) rồi chạy lại — bạn sẽ thấy hàng mới xuất hiện với order_id là NULL, đúng
như dự đoán.
FULL OUTER JOIN native. Kỹ thuật giả lập kinh điển: ghép LEFT JOIN và
RIGHT JOIN bằng UNION (không phải UNION ALL, để tự loại trùng
phần giao nhau xuất hiện ở cả 2 vế):
SELECT c.full_name, o.order_id FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id
UNION
SELECT c.full_name, o.order_id FROM customers c RIGHT JOIN orders o ON c.customer_id = o.customer_id;
Vế đầu bắt hết "trái + phần giao", vế sau bắt hết "phải + phần giao" — UNION hợp nhất
và loại bản sao trùng của phần giao, cho đúng kết quả FULL OUTER JOIN dù engine không
hỗ trợ cú pháp đó trực tiếp. Đây là kiến thức đáng biết ngay cả khi dùng SQLite/PostgreSQL hiện đại
— bạn sẽ gặp lại kỹ thuật UNION này khi làm việc với MySQL hoặc code base cũ.
5. CROSS JOIN & Tích Descartes: Cẩn Trọng Với Phép Nhân
CROSS JOIN không dựa vào điều kiện khớp nào cả — nó ghép mọi hàng của
bảng trái với mọi hàng của bảng phải, tạo ra M × N hàng kết quả (tích
Descartes/Cartesian product). Đây là loại JOIN duy nhất
không có mệnh đề ON.
Tình huống 1 — Dùng đúng mục đích: tạo khung báo cáo đầy đủ tổ hợp: cần 1 bảng liệt kê mọi tổ hợp (danh mục sản phẩm × trạng thái đơn hàng) để làm khung pivot table, kể cả tổ hợp chưa từng có dữ liệu thực tế (hiện 0 thay vì thiếu hẳn dòng đó).
SELECT DISTINCT p.category, o.status
FROM (SELECT DISTINCT category FROM products) p
CROSS JOIN (SELECT DISTINCT status FROM orders) o
ORDER BY p.category, o.status;
Đây là kỹ thuật thật sự hữu ích trong báo cáo: đảm bảo mọi ô trong ma trận pivot đều tồn tại (kể cả ô
sẽ hiện giá trị 0 sau khi LEFT JOIN thêm dữ liệu thực), thay vì thiếu hẳn hàng cho tổ hợp
chưa phát sinh — rất quan trọng khi vẽ biểu đồ hoặc export báo cáo cố định số dòng.
Tình huống 2 — Tai nạn thường gặp: quên mệnh đề ON khi định viết INNER JOIN:
-- Gõ nhầm/quên ON — đây vô tình là CROSS JOIN, không phải INNER JOIN!
SELECT o.order_id, oi.product_id
FROM orders o, order_items oi;
Cú pháp FROM a, b (dùng dấu phẩy thay vì JOIN ... ON) là cách viết
CROSS JOIN kiểu cũ (SQL-89) — rất dễ gõ nhầm khi định viết INNER JOIN nhưng
quên mất điều kiện nối. Với 28 orders và 34 order_items, câu lệnh trên trả
về 28 × 34 = 952 hàng hoàn toàn vô nghĩa (mọi đơn hàng ghép với mọi dòng sản phẩm, kể
cả những cặp chẳng liên quan gì tới nhau) thay vì con số đúng là 34 hàng (mỗi
order_item chỉ thuộc đúng 1 đơn hàng). Với bảng có hàng triệu dòng, lỗi này có thể tạo ra
hàng tỷ dòng kết quả, làm treo cả server. Luôn ưu tiên cú pháp JOIN ... ON tường minh —
không bao giờ dùng cú pháp phẩy ngăn cách bảng trong code sản xuất.
6. Self-Join: Một Bảng Tự Nối Với Chính Nó
Self-join là JOIN giữa 1 bảng với chính nó, dùng khi cần so sánh các
hàng trong cùng 1 bảng với nhau. TechMart không có cột phân cấp kiểu quản lý/nhân viên, nhưng vẫn có 2
tình huống self-join rất thực tế không cần cấu trúc cây:
Tình huống 1 — Tìm khách hàng cùng quốc gia: gợi ý sự kiện offline theo khu vực, cần
ghép mọi cặp khách hàng khác nhau nhưng cùng country.
SELECT c1.full_name AS khach_1, c2.full_name AS khach_2, c1.country
FROM customers c1
INNER JOIN customers c2
ON c1.country = c2.country AND c1.customer_id < c2.customer_id
ORDER BY c1.country;
Vì đang nối bảng customers với chính nó, bắt buộc phải đặt
2 alias khác nhau (c1, c2) để SQL phân biệt được "bản sao
nào của customers đang được nhắc tới" ở mỗi cột — không có alias, câu lệnh sẽ mơ hồ và
báo lỗi. Điều kiện c1.customer_id < c2.customer_id (thay vì !=) là mẹo
quan trọng để tránh 2 vấn đề cùng lúc: (1) khách tự ghép với chính mình (c1.customer_id = c2.customer_id), và (2) mỗi cặp bị đếm 2 lần theo 2 chiều (An-Bình và Bình-An).
Tình huống 2 — Phân tích khoảng cách giữa các đơn hàng liên tiếp: với mỗi khách có từ 2 đơn trở lên, tìm cặp đơn hàng gần nhau nhất để đo hành vi mua lặp lại (customer retention).
SELECT o1.customer_id, o1.order_id AS don_truoc, o2.order_id AS don_sau,
o1.order_date AS ngay_truoc, o2.order_date AS ngay_sau,
julianday(o2.order_date) - julianday(o1.order_date) AS so_ngay_cach
FROM orders o1
INNER JOIN orders o2
ON o1.customer_id = o2.customer_id
AND o1.order_id < o2.order_id
WHERE o1.customer_id IS NOT NULL
ORDER BY o1.customer_id, so_ngay_cach;
julianday() chuyển chuỗi ngày ISO-8601 (xem lại Bài 1) thành số ngày Julian, cho phép trừ
trực tiếp để ra số ngày cách nhau. Lưu ý điều kiện o1.order_id < o2.order_id không đảm
bảo o1 luôn là đơn gần nhất trước o2 theo thời gian nếu 1 khách có
3 đơn trở lên (sẽ sinh ra mọi cặp, không chỉ cặp liên tiếp) — đây là bài toán "tìm hàng kế tiếp" mà
Bài 7 (Window Functions) sẽ giải gọn hơn nhiều bằng
LAG()/LEAD(), thay vì self-join.
7. Alias: Tên Ngắn Gọn Cho Bảng & Cột
Mọi ví dụ ở trên đều dùng AS (hoặc bỏ luôn từ khoá AS, chỉ viết tên ngay sau
bảng/cột — cả 2 cách đều hợp lệ trong SQLite) để đặt tên ngắn cho bảng (orders o) và cột
tính toán (oi.quantity * oi.unit_price AS line_total). Alias bảng
bắt buộc trong self-join (không thể phân biệt 2 bản sao cùng tên bảng nếu không đặt
tên khác nhau), và nên dùng ở mọi JOIN nhiều bảng để tránh gõ lại tên bảng đầy đủ
hàng chục lần, đồng thời giải quyết trường hợp 2 bảng có cột trùng tên (ví dụ cả
orders và order_items đều không có cột trùng tên trong TechMart, nhưng nếu
có, không dùng alias sẽ gây lỗi "ambiguous column name").
Bảng Tổng Hợp 5 Loại JOIN
| Loại JOIN | Giữ lại hàng nào | Dùng khi nào |
|---|---|---|
INNER JOIN |
Chỉ hàng khớp ở cả 2 bảng | Chỉ quan tâm dữ liệu có liên kết đầy đủ (hoá đơn, doanh thu theo danh mục) |
LEFT JOIN |
Toàn bộ bảng trái + phần khớp bên phải | "Toàn bộ X, kèm Y nếu có" — báo cáo không được bỏ sót X (mọi khách hàng, mọi sản phẩm) |
RIGHT JOIN |
Toàn bộ bảng phải + phần khớp bên trái | Tương đương LEFT JOIN đổi chỗ bảng — hiếm dùng trực tiếp trong thực tế |
FULL OUTER JOIN |
Toàn bộ cả 2 bảng, khớp được thì ghép chung 1 hàng | Đối chiếu dữ liệu 2 nguồn, tìm bất thường ở cả 2 hướng cùng lúc |
CROSS JOIN |
Mọi tổ hợp (M × N), không cần điều kiện khớp | Cố ý tạo khung tổ hợp đầy đủ (pivot); nếu vô tình xảy ra — luôn là lỗi |
Sân chơi tương tác: Venn Diagram & SQL Workbench Trên Dataset TechMart
Bấm 1 loại JOIN bên trái để tô sáng đúng vùng Venn diagram tương ứng — đồng thời truy vấn thật sẽ tự
chạy trên engine SQLite-WASM bên dưới với đúng 2 bảng orders (vòng tròn trái) và
customers (vòng tròn phải), cho bạn thấy trực tiếp số dòng kết quả khớp với vùng được tô
sáng. Sau đó có thể chỉnh sửa tự do trong ô soạn thảo, hoặc bấm 1 nút tình huống khác để nạp sẵn các
ví dụ 3-4 bảng, self-join, hay CROSS JOIN ở trên.
customers
| Cột | Kiểu |
|---|---|
| customer_id | INTEGER PRIMARY KEY |
| full_name | TEXT NOT NULL |
| TEXT | |
| country | TEXT |
| signup_date | TEXT |
| is_active | INTEGER |
products
| Cột | Kiểu |
|---|---|
| product_id | INTEGER PRIMARY KEY |
| product_name | TEXT NOT NULL |
| category | TEXT |
| unit_price | REAL |
| stock_quantity | INTEGER |
orders
| Cột | Kiểu |
|---|---|
| order_id | INTEGER PRIMARY KEY |
| customer_id | INTEGER (NULL = khách vãng lai) |
| order_date | TEXT |
| status | TEXT |
| total_amount | REAL |
order_items
| Cột | Kiểu |
|---|---|
| order_item_id | INTEGER PRIMARY KEY |
| order_id | INTEGER |
| product_id | INTEGER |
| quantity | INTEGER |
| unit_price | REAL |
Đang tải...
⬇ Tải file schema + dữ liệu mẫu TechMart (.sql) — chạy
được trực tiếp bằng sqlite3 CLI hoặc DB Browser for SQLite trên máy bạn.
Trắc nghiệm ôn tập
Câu 1: Khác biệt cốt lõi giữa INNER JOIN và LEFT JOIN là gì?
Trắc nghiệm ôn tập
Câu 2: Truy vấn
... FROM orders o LEFT JOIN customers c ON o.customer_id = c.customer_id WHERE c.country =
'Vietnam'
có vấn đề gì?
Trắc nghiệm ôn tập
Câu 3: Vì sao trong thực tế lập trình viên hiếm khi dùng RIGHT JOIN trực tiếp?
Trắc nghiệm ôn tập
Câu 4: Engine không hỗ trợ FULL OUTER JOIN native (ví dụ MySQL) có thể giả lập nó bằng
cách nào?
Trắc nghiệm ôn tập
Câu 5: Câu lệnh SELECT * FROM orders o, order_items oi; (cú pháp phẩy, không có
WHERE/ON) với 28 hàng orders và 34 hàng
order_items sẽ trả về bao nhiêu hàng?
Trắc nghiệm ôn tập
Câu 6: Vì sao self-join bắt buộc phải dùng 2 alias khác nhau cho cùng 1 bảng (ví dụ
customers c1 và customers c2)?