# 内连接与左连接

> 连接匹配的行，并使用 INNER JOIN 和 LEFT JOIN 保留未匹配的行。

---

LLMS 索引： [llms.txt](/llms.txt)

---

连接会根据匹配条件，把两个（或更多）表中的行组合起来。它可以把关系数据重建成应用程序所需的形状。`INNER JOIN` 只保留两侧都满足条件的行；`LEFT JOIN`（也称 `LEFT OUTER JOIN`）保留左侧的每一行，右侧没有匹配时用 NULL 填充。掌握连接，就是掌握 SQL。

## 本章要点 {#learn}

- `INNER JOIN` 配合 `ON`：只保留匹配的行
- `LEFT JOIN`：保留左侧全部行，未匹配时右侧为 NULL
- 使用表别名（`c`、`o`）提高可读性
- 了解在 JOIN 目标中筛选与在 `WHERE` 子句中筛选的区别
- 使用 `USING (col)` 简写 `ON a.col = b.col`

## 示例 {#example}

```sql {num="29.1" caption="内连接与左连接"}
-- Two small tables for joins
DROP TABLE IF EXISTS orders_example;
DROP TABLE IF EXISTS customers_example;

CREATE TABLE customers_example (
  id   integer PRIMARY KEY,
  name text NOT NULL
);

CREATE TABLE orders_example (
  id          integer PRIMARY KEY,
  customer_id integer,                      -- intentionally not a real FK so we can show unmatched rows
  total       numeric(10, 2) NOT NULL
);

INSERT INTO customers_example VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Carol');
INSERT INTO orders_example   VALUES (1, 1, 50), (2, 1, 30), (3, 99, 10);  -- order 3 points at a non-existent customer

-- INNER JOIN: only rows that match on both sides
SELECT c.name, o.id AS order_id, o.total
FROM customers_example c
INNER JOIN orders_example o ON o.customer_id = c.id
ORDER BY o.id;

-- LEFT JOIN: every customer, even those without orders
SELECT c.name, o.id AS order_id, o.total
FROM customers_example c
LEFT JOIN orders_example o ON o.customer_id = c.id
ORDER BY c.id, o.id;

-- Filtering on the JOIN vs in WHERE matters for LEFT JOIN.
-- "Customers and their orders > 40, even if they have none":
SELECT c.name, o.id, o.total
FROM customers_example c
LEFT JOIN orders_example o ON o.customer_id = c.id AND o.total > 40
ORDER BY c.id;

-- vs "customers who have orders > 40" — the WHERE turns the LEFT into an INNER:
SELECT c.name, o.id, o.total
FROM customers_example c
LEFT JOIN orders_example o ON o.customer_id = c.id
WHERE o.total > 40
ORDER BY c.id;
```

`ON` 子句是连接条件。它可以是任意布尔表达式，不过通常写成 `parent.id = child.parent_id`。`ON` 中的条件属于连接本身——对于 `LEFT JOIN`，即使右侧没有满足 `ON` 的匹配行，左侧行也会保留下来。`WHERE` 中的条件会在连接之后应用，因此对 `LEFT JOIN` 使用 `WHERE child.col = 'x'` 会过滤掉未匹配的（NULL）行，实际上使连接表现为 `INNER JOIN`。

`USING (col)` 是在两侧列名相同时的简写：`USING (customer_id)` 等价于 `ON a.customer_id = b.customer_id`，而结果行中只会有一个 `customer_id` 列，不是两个。

第 3 个订单的 `customer_id = 99`（没有该 id 的客户）。`INNER JOIN` 会丢弃它；`LEFT JOIN` 会保留没有订单的客户（Bob、Carol）。两者结合起来才能看到完整情况。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/inner-and-left-join.sql postgres
...
 name  | order_id | total
-------+----------+-------
 Alice |        1 | 50.00
 Alice |        2 | 30.00
(2 rows)

 name  | order_id | total
-------+----------+-------
 Alice |        1 | 50.00
 Alice |        2 | 30.00
 Bob   |          |
 Carol |          |
(4 rows)
 ...
```

## 常见陷阱 {#pitfalls}

- 使用 `WHERE right_table.col = 'x'` 过滤 `LEFT JOIN`，会意外丢掉你原本想保留的未匹配行。如果条件属于匹配条件，就把它放在 `ON` 中；如果想在连接后再筛选，就放在 `WHERE` 中。
- 在没有索引的列上连接时，规划器会改用哈希连接或归并连接，而这可能扫描整张表。请为外键列建立索引。
- 笛卡尔积：`CROSS JOIN`，或不带连接谓词的逗号分隔 `FROM` 列表，会产生 `m × n` 行。普通的 `INNER JOIN` 需要 `ON` 或 `USING`；PostgreSQL 不会对合法的交叉连接发出警告。
- `LEFT JOIN ... GROUP BY` 是“统计每个父项的子项数量，同时包括没有子项的父项”的自然写法。使用 `count(child.id)`（而不是 `count(*)`），这样未匹配的行才会得到 0。

> [!TIP] 提示
> 选择简短且有意义的别名。`customers_example c` 和 `orders_example o` 比 `customers_example cs1` 和 `orders_example o1` 更易读。许多风格指南建议使用由表名派生的 1–3 个字符的别名。

> [!NOTE] 动手练习
> 把 `INNER JOIN` 改成 `LEFT JOIN`，看看 Carol 如何带着 NULL 出现。试试上面的两种筛选写法，确认只有其中一种会保留未匹配的客户。然后使用 `USING`：`SELECT * FROM customers_example c JOIN orders_example o ON o.customer_id = c.id;` 会变成 `SELECT * FROM customers_example c JOIN orders_example o USING (id);`——等等，这里两张表的列名不同，所以你需要先重命名其中一列。换用两张共享同一个列名的表试试。

## 下载 {#downloads}

SQL 文件：[inner-and-left-join.sql](/examples/source/inner-and-left-join.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
