最基本的连接
订单存的是 customer_id,客户表里才有名字。想一起看到,就要告诉数据库这两张表怎么对上。
1
SELECT o.order_number,
o.ordered_at,
o.total,
c.first_name || ' ' || c.last_name AS customer,
c.country
FROM orders o
JOIN customers c ON c.id = o.customer_id
ORDER BY o.ordered_at DESC
LIMIT 20;
结果 — 共 20 行,这里是前 5 行
| order_number | ordered_at | total | customer | country |
|---|---|---|---|---|
| ORD-2025-002480 | 2025-12-28 23:52:14 | 6333.77 | Elena Meyer | CA |
| ORD-2025-000505 | 2025-12-28 11:16:39 | 2276.51 | Diego Rossi | IT |
| ORD-2025-000940 | 2025-12-28 07:28:46 | 11000.68 | Freya Choi | BR |
| ORD-2025-001581 | 2025-12-28 06:12:38 | 2645.06 | Jonas Lee | JP |
| ORD-2025-000995 | 2025-12-28 05:47:53 | 2584.02 | Leo Haddad | US |
浏览器会另开一个标签页打开 Planche,示例数据库已经载入,这条查询也已经放好。本页其他示例都会复用同一个标签页;在你按下 F9 之前,什么都不会执行。
这里有三样东西值得点名。单写 JOIN 就是 INNER JOIN。ON c.id = o.customer_id 是连接条件——左边的行按什么和右边的行配对。而 orders o 给表起了个短别名,正因为有它,你才能写 o.total 和 c.country,而不用每次重复整个表名。
一旦两张表有同名的列,别名就不再是可选项了。这两张表都有 id;不加限定的 id 是有歧义的,数据库会直接拒绝这条查询。
不止两张表
连接是可以串的。订单里的商品隔着两步:orders → order_items → products。
2
SELECT o.order_number,
p.name AS product,
oi.quantity,
oi.unit_price
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status = 'delivered'
ORDER BY o.ordered_at DESC
LIMIT 25;
结果 — 共 25 行,这里是前 5 行
| order_number | product | quantity | unit_price |
|---|---|---|---|
| ORD-2025-000505 | Zen Boots | 1 | 700.65 |
| ORD-2025-000505 | Echo Backpack 40L Plus | 1 | 1570.96 |
| ORD-2025-001581 | Atlas Phone Pro Max | 1 | 1546.38 |
| ORD-2025-001581 | Pulse Headphones | 2 | 548.09 |
| ORD-2025-000995 | Aria Chrono | 2 | 1289.56 |
每个 JOIN 都是在已有结果上再挂一张表,所以四表连接不过是再加一行。从上往下读就是"订单,连上它的明细,再连上每条明细对应的商品"。
保留没有匹配的行
内连接会丢掉配不上对的一切。从没下过单的客户干脆就从结果里消失了。LEFT JOIN 会保留左表的每一行,右边没得配时就用 NULL 填上。
3
SELECT c.id,
c.first_name,
c.last_name,
o.order_number,
o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
ORDER BY c.id
LIMIT 25;
结果 — 共 25 行,这里是前 5 行
| id | first_name | last_name | order_number | total |
|---|---|---|---|---|
| 1 | Liam | Fischer | ORD-2022-001596 | 1317.56 |
| 1 | Liam | Fischer | ORD-2025-001723 | 2664.56 |
| 1 | Liam | Fischer | ORD-2025-003142 | 4609.77 |
| 2 | Mateo | Costa | ORD-2025-002473 | 3760.6 |
| 3 | Yuki | Jang | ORD-2025-003527 | 7569.3 |
于是"找出缺了什么"这类查询成为可能:先全部保留,再只留下没配上的那些行。
4
-- The 95 customers who have never ordered anything.
SELECT c.id, c.first_name, c.last_name, c.country
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL
ORDER BY c.id
LIMIT 25;
结果 — 共 25 行,这里是前 5 行
| id | first_name | last_name | country |
|---|---|---|---|
| 14 | Maya | Moreau | US |
| 17 | Nora | Müller | IT |
| 20 | Sana | Park | KR |
| 30 | Ravi | Park | KR |
| 33 | Oliver | Park | IN |
WHERE o.id IS NULL 初看很怪——orders.id 是主键,在表里绝不可能为空。但它在这个结果里可以为空,因为 LEFT JOIN 为那些配不上的行凭空造出了这些 NULL。这些行就是那 95 位从没下过单的客户。
ON 还是 WHERE:不是同一个问题
坑在这里。下面两条查询都写了 status = 'cancelled',但实际起到 LEFT JOIN 作用的只有一条。
5
-- The condition is part of the join: every customer is kept,
-- and only cancelled orders are attached to them.
SELECT c.id, c.first_name, o.order_number, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'cancelled'
ORDER BY c.id
LIMIT 25;
结果 — 共 25 行,这里是前 5 行
| id | first_name | order_number | status |
|---|---|---|---|
| 1 | Liam | NULL | NULL |
| 2 | Mateo | NULL | NULL |
| 3 | Yuki | ORD-2025-003530 | cancelled |
| 4 | Ines | NULL | NULL |
| 5 | Leo | NULL | NULL |
条件在 ON 里,所以它是配对过程的一部分:每个客户都保留,有已取消订单的就挂上一条。没有已取消订单的客户照样出现,右边是 NULL。
6
-- The same condition in WHERE runs after the join and throws away
-- every row where it is not true — including the NULLs the LEFT JOIN made.
SELECT c.id, c.first_name, o.order_number, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'cancelled'
ORDER BY c.id
LIMIT 25;
结果 — 共 25 行,这里是前 5 行
| id | first_name | order_number | status |
|---|---|---|---|
| 3 | Yuki | ORD-2025-003530 | cancelled |
| 6 | Nora | ORD-2023-001551 | cancelled |
| 6 | Nora | ORD-2022-002224 | cancelled |
| 7 | Priya | ORD-2022-003262 | cancelled |
| 10 | Elena | ORD-2025-000182 | cancelled |
这里的条件写在 WHERE 中,会在连接之后判断。LEFT JOIN 产生的 NULL 行无法满足 NULL = 'cancelled',因此会被排除。结果就等同于 INNER JOIN。
通常,LEFT JOIN 右表的筛选条件应写在 ON 中,左表的筛选条件写在 WHERE 中。INNER JOIN 中两种写法的结果相同,因此很容易忽略这个区别。
把一张表和它自己连接
没人规定两边必须是不同的表。员工有一个指向另一名员工的 manager_id,所以这张表可以用两个不同的别名和自己连接。
7
SELECT e.first_name || ' ' || e.last_name AS employee,
e.position,
m.first_name || ' ' || m.last_name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id
ORDER BY manager, employee
LIMIT 25;
结果 — 共 25 行,这里是前 5 行
| employee | position | manager |
|---|---|---|
| Ava Müller | Director | NULL |
| Hana Tanaka | Director | NULL |
| Hugo Ahmed | Director | NULL |
| Lucas Romero | Director | NULL |
| Maya Müller | Director | NULL |
e 和 m 是同一张表被看了两次,让它成立的正是别名。这里用 LEFT JOIN,是因为最上面那十个人没有上级,用内连接会把他们悄悄漏掉。
现在你能做什么
你可以跨整套结构提问了。还做不到的是把答案汇总起来:数订单、算营收、求平均,都在下一课。