首页 / SQLite 入门教程 / 多表连接 JOIN

SQLite 入门教程

多表连接 JOIN

本教程共 50 篇 · 第 27 篇 · 更新于 2026-07-31

sqlitejoininner-joinleft-joincross-join多表查询

27. 多表连接 JOIN

本节目标:学完本章你能用 INNER JOIN、LEFT JOIN、CROSS JOIN 把两张及以上的表按条件拼成一张结果集,并清楚每种连接”会保留哪些行、会补哪些 NULL”。

到目前为止,我们都是在单张表里折腾数据。但真实世界里,数据往往是”分散”在多张表里的:用户信息放在 users,订单信息放在 orders,它们通过一个”外键”字段(这里是 orders.user_id)串起来。当你既想知道”谁下了单”、又想知道”下了多少金额的订单”时,就必须把两张表横向拼到一起——这件事在 SQL 里就叫 JOIN(连接)

回忆一下我们全局统一的示例表结构:

-- 用户表
CREATE TABLE users (
  id    INTEGER PRIMARY KEY,
  name  TEXT,
  age   INTEGER,
  email TEXT
);

-- 订单表,user_id 指向 users.id
CREATE TABLE orders (
  id        INTEGER PRIMARY KEY,
  user_id   INTEGER,
  amount    REAL,
  created_at TEXT
);

注意 orders.user_id 这一列。从语义上说它”应该”指向 users.id,但 SQLite 并不会因为你”心里这么想”就自动保证这一点——这正是后面 WARNING 要强调的事。先记住这个表关系:一个用户(users 一行)可以对应多笔订单(orders 多行),这是典型的”一对多”关系。

为什么需要 JOIN,而不是两次查询

你当然可以先查 users 拿到一批用户,再逐个人去 orders 里找他的订单。但这样做有两个麻烦:第一,要在程序里写循环、做匹配,代码啰嗦;第二,数据库本来就是为”集合运算”而生的,它在底层对连接做了大量优化,自己拼不如交给它拼。JOIN 的本质,就是告诉 SQLite:“请把 A 表和 B 表按某个条件对齐,凡是能对上的,就拼成一行返回给我。”

类比一下:两张纸,一张写”人名 + 身份证号”,一张写”订单号 + 身份证号 + 金额”。JOIN 就是按”身份证号”这个共同字段,把两张纸并排贴在一起。贴的规则不同,得到的结果也不同——这正是 INNER、LEFT、CROSS 三种连接的区别。

INNER JOIN:只保留能对上的行

INNER JOIN(内连接) 是最常用的连接。它的语义是:左表和右表都取,但结果里只保留两边都能对上连接条件的行;对不上的,直接丢弃。

sqlite> SELECT users.name, orders.amount
   ...> FROM users
   ...> INNER JOIN orders ON orders.user_id = users.id;

这条语句的含义:从 usersorders 里,凡是 orders.user_id 能在 users.id 中找到匹配的那一行,就把 users.nameorders.amount 拼成一行输出。如果一个用户从来没下过单(orders 里没有他的记录),他在 INNER JOIN 的结果里不会出现;反过来,如果一笔订单的 user_idusers 里找不到对应的人(脏数据),这笔订单也不会出现。

ON 后面跟的就是”连接条件”,它决定了两张表怎么对齐。除了 ON,当两个表的连接列名完全相同时,还可以用更短的 USING 写法:

sqlite> SELECT users.name, orders.amount
   ...> FROM users
   ...> INNER JOIN orders USING (id);  -- 仅当连接列都叫 id 时可用,本例不适用,演示语法
Note

INNER JOIN 是默认的连接类型,写 JOIN 不加前缀时,SQLite 就当作 INNER JOIN 处理。列名冲突时用”表名.列名”(如 users.id)来限定,能避免”到底取哪一列”的歧义。

要让结果更易读,可以给表起别名(alias),尤其是表名较长时:

sqlite> SELECT u.name, o.amount, o.created_at
   ...> FROM users AS u
   ...> INNER JOIN orders AS o ON o.user_id = u.id
   ...> ORDER BY o.amount DESC;

AS uAS o 只是给表取的小名,本句之内有效,纯属为了方便。

LEFT JOIN:左表全保留,右表对不上就补 NULL

LEFT JOIN(左连接) 的语义是:左表(FROM 后面那张)的每一行都一定出现在结果里;至于右表,能对上的就拼过来,对不上的那一列用 NULL 补齐。

sqlite> SELECT u.name, o.amount
   ...> FROM users AS u
   ...> LEFT JOIN orders AS o ON o.user_id = u.id;

这下差别就出来了:哪怕某个用户一笔订单都没有,他依然会出现在结果里,只是 o.amount 那一侧是 NULL。这个特性非常实用——比如你想统计”每个用户下了多少单”,包括那些 0 单的用户,就必须用 LEFT JOIN,否则 INNER JOIN 会把 0 单用户直接过滤掉。

LEFT JOIN 还能用来找”孤儿数据”,也就是”在左表存在、但在右表找不到对应”的行。方法是接一个 WHERE 右表主键 IS NULL

sqlite> SELECT u.name
   ...> FROM users AS u
   ...> LEFT JOIN orders AS o ON o.user_id = u.id
   ...> WHERE o.id IS NULL;   -- 找出从未下过单的用户

LEFT JOINLEFT OUTER JOIN 是完全等价的写法,加不加 OUTER 都行。

Tip

实用技巧:统计每个用户的订单数(含 0 单用户),用 COUNT(o.id) 而不是 COUNT(*)COUNT(o.id) 只数非 NULL 的订单 id,正好是订单数;而 COUNT(*) 会把左表每一行都算 1,等于用户数而非订单数。

sqlite> SELECT u.name, COUNT(o.id) AS order_count
   ...> FROM users AS u
   ...> LEFT JOIN orders AS o ON o.user_id = u.id
   ...> GROUP BY u.id, u.name;

CROSS JOIN:笛卡尔积,谨慎使用

CROSS JOIN(交叉连接) 最特别:它没有任何连接条件,直接把左表的每一行和右表的每一行两两配对。如果左表有 n 行、右表有 m 行,结果就是 n × m 行。这种”全配对”的结果在数学上叫笛卡尔积(Cartesian product)

sqlite> SELECT u.name, o.id
   ...> FROM users AS u
   ...> CROSS JOIN orders AS o;

什么时候需要它?典型场景是”生成组合”。比如你有一张”产品表”和一张”月份表”,想生成”每个产品 × 每个月”的销售计划基底,CROSS JOIN 一次就搞定。但要注意:如果不小心在普通 JOIN 上漏写了 ON 条件,SQLite 也可能退化为笛卡尔积,行数会爆炸式增长,所以写连接时务必确认连接条件没丢。

Warning

常见坑:外键默认是关闭的!orders.user_id 指向 users.id 只是”我们约定”的语义,SQLite 并不会自动阻止你插入一个 user_idusers 里根本不存在的订单,也不会因为删了某个用户而自动删掉他的订单。如果你想让数据库真正”强制”这种参照关系(插入非法 user_id 时报错),必须显式打开外键检查,而且每次新连接都要重新打开

sqlite> PRAGMA foreign_keys = ON;

同时建表时要写上 FOREIGN KEY(user_id) REFERENCES users(id)。JOIN 本身只负责”按条件拼数据”,不负责”保证数据合法”,二者不要混淆。

三种连接怎么选

  • 只要”两边都对得上”的数据 → INNER JOIN(最常用,也是默认的 JOIN)。
  • 要”左表全保留,右表可选匹配”(如统计含 0 的单、找孤儿记录)→ LEFT JOIN
  • 要”穷举所有两两组合”(如产品 × 月份) → CROSS JOIN,且一定要想清楚结果行数会不会太大。

还有一个常被问到的问题:SQLite 支持 RIGHT JOIN 和 FULL OUTER JOIN 吗?答案是:SQLite 不支持 RIGHT JOIN 和 FULL OUTER JOIN。如果你”想要右连接的效果”,标准做法是把两张表调换左右位置,改用 LEFT JOIN 即可;想要”全外连接”(左右都没匹配的行都要),则需要用 LEFT JOIN 与 RIGHT 思路的 UNION 组合来模拟,这部分我们留到 UNION 章节再展开。

类比小结

把 JOIN 想象成”按共同字段把两张纸并排贴”:

  • INNER JOIN = 只贴两边都有的;
  • LEFT JOIN = 左边那张纸每行都留着,右边贴不上的空着(NULL);
  • CROSS JOIN = 左边每行和右边每行全部两两配对,不管有没有共同字段。

掌握这三种,日常 90% 的多表查询都能拿下。它们可以和前面学过的 WHEREGROUP BYORDER BYLIMIT 自由组合——连接先把数据拼好,后面的过滤、分组、排序照常生效。下一章我们再看另一种”嵌套”获取数据的方式:子查询与自连接。