JOIN
在关系型数据库中,数据通常被分散存储在多个相关的表中。为了从这些分散的表中组合并检索数据,我们需要使用 SQL JOIN(连接)操作。SQLite 支持多种类型的 JOIN,但与 PostgreSQL 或 MySQL 等其他数据库相比,它也有一些独特的限制(例如不支持直接的 RIGHT JOIN)。
本文将详细讲解 SQLite 中各种 JOIN 的用法、区别以及如何模拟 SQLite 不直接支持的连接类型。
1. 准备工作:测试数据
为了更好地理解各种 JOIN 的区别,我们需要建立两个简单的表:departments(部门表)和 employees(员工表)。
请在您的 SQLite 环境中执行以下 SQL 语句来初始化数据:
数据概览:
- 研发部 有张三、李四。
- 市场部 有王五。
- 财务部 目前没有员工。
- 赵六 目前没有归属部门。
2. INNER JOIN(内连接)
这是最常用的一种连接类型。INNER JOIN 只返回两个表中匹配的行。如果某一行在另一个表中找不到匹配项,则该行不会出现在结果中。
语法
(注:INNER 关键字是可选的,只写 JOIN 默认就是内连接)
示例:查询所有已分配部门的员工及其部门名称
结果:
分析:
- "赵六" 被排除了,因为他的
dept_id是 NULL,无法匹配部门。 - "财务部" 被排除了,因为它在员工表中没有对应的记录。
3. LEFT JOIN(左连接)
LEFT JOIN(也称为 LEFT OUTER JOIN)返回左表(FROM 子句中的表)的所有行,以及右表(JOIN 子句中的表)中匹配的行。如果右表中没有匹配项,则结果中右表的部分显示为 NULL。
语法
示例:查询所有员工及其部门名称(包括未分配部门的员工)
结果:
分析:
- "赵六" 出现在结果中,尽管他没有对应的部门,部门名称列显示为
NULL。 - 此查询保留了左表(员工表)的全部数据。
4. CROSS JOIN(交叉连接)
CROSS JOIN 会生成两个表的笛卡尔积。即左表的每一行都会与右表的每一行进行组合。如果左表有 X 行,右表有 Y 行,结果集将会有 X * Y 行。通常不需要 ON 子句。
语法
示例:列出员工和部门的所有可能组合
结果: 结果将包含 12 行(4名员工 * 3个部门),无论他们实际是否属于该部门。这种连接在生成测试数据或穷举所有组合场景时非常有用。
5. SELF JOIN(自连接)
自连接并不是一种特殊的关键字,而是指一个表与它自己进行连接。这通常用于处理层级数据(如上下级关系)或比较同一表中的行。
注意: 进行自连接时,必须使用表别名来区分同一个表的两个实例。
场景假设
假设我们在员工表中增加一列 manager_id,用于存储上级领导的 ID。
- 张三 (ID 101) 的上级是 NULL(他是大老板)。
- 李四 (ID 102) 的上级是 张三 (101)。
示例:查询员工及其直接上级的名字
这里我们将 employees 表分别命名为 sub(下属表)和 mgr(领导表),通过 manager_id 和 emp_id 进行关联。
结果:
6. SQLite 不支持的 JOIN 及解决方案
SQLite 目前不支持 RIGHT JOIN(右连接)和 FULL OUTER JOIN(全外连接)的直接语法。但是,我们可以通过其他方式轻松实现相同的效果。
6.1 模拟 RIGHT JOIN
RIGHT JOIN 的逻辑是:返回右表的所有行,以及左表匹配的行。
解决方案: 交换表的顺序并使用 LEFT JOIN。
需求:列出所有部门及其员工(包括没有员工的部门)
这本应是 employees RIGHT JOIN departments。
SQLite 写法:
结果:
6.2 模拟 FULL OUTER JOIN
FULL OUTER JOIN 的逻辑是:只要其中一个表中存在匹配,就返回行(即 左表结果 + 右表结果)。
解决方案: 使用 LEFT JOIN 结合 UNION ALL 或 UNION。
标准的模拟方法是:(A 左连 B) UNION (B 左连 A)。
需求:列出所有员工和所有部门,无论是否匹配 即包含:有部门的员工、没部门的员工、没员工的部门。
SQLite 写法:
注意:UNION 会自动去除重复的行(即那些既有员工又有部门的完美匹配行),从而得到正确的全外连接结果。如果出于性能考虑且确定没有重叠,也可以结合 WHERE 条件使用 UNION ALL。
结果:
7. 总结与最佳实践
不同 JOIN 的速查表
性能建议
- 使用索引: 确保用于
ON子句连接的列(如外键dept_id)上建立了索引。这对于大型表的 JOIN 性能至关重要。 - 明确列名: 在 SELECT 子句中,尽量使用
table_name.column_name或别名(e.emp_name),而不是仅仅写列名,这样可以避免列名冲突并提高代码可读性。 - 避免笛卡尔积: 除非确实需要,否则不要忘记在 JOIN 语句中添加
ON条件,否则会意外产生巨大的 CROSS JOIN 结果集。

