1.1. 为什么需要 JOIN?
当数据分散在多个表中时,JOIN 允许你通过共同的列(如主键和外键)合并这些表的数据,从而提取更完整的信息。我们将以sqlite数据库语法与实践这篇博客锁建立的表为例来理解JOIN的使用场景。为了好理解LEFT JOIN 和 RIGHT JOIN,先准备一些数据:
--插入一些没有未分配部门的员工
INSERT INTO Employees (name, email, hire_date) VALUES
('Alice', '[email protected]', '2011-03-20'),
('Bob', '[email protected]', '2012-05-25'),
('Charlie', '[email protected]', '2013-07-30'),
('David', '[email protected]', '2014-09-10'),
('Eve', '[email protected]', '2015-11-15'),
('Frank', '[email protected]', '2016-01-20'),
('Grace', '[email protected]', '2017-03-25');
--插入一些部门 还未分配员工的
INSERT INTO Departments(department_name, parent_department_id) VALUES
('三级部门3', 4),--6
('三级部门4', 5),--7
('三级部门5', 4),--6
('三级部门6', 5),--7
('三级部门7', 4),--6
('三级部门8', 5);--7
Tips:可以用vscode新家.sql文件来提高编辑效率。
1.2. SQLite 支持的 JOIN 类型
1.2.1. INNER JOIN
-
- 作用:返回两个表中匹配的行(仅保留双方都存在的记录)。
语法:
SELECT 列
FROM 表1
INNER JOIN 表2 ON 表1.列 = 表2.列;
示例:根据员工的部门ID查询员工所在的部门,列显示 姓名 邮件 部门名称
SELECT e.name, e.email, d.department_name
FROM Employees e
INNER JOIN Departments d ON e.department_id = d.department_id;
结果:
1.2.2. LEFT JOIN(LEFT OUTER JOIN)
-
- 作用:返回左表(
表1
)的所有行,即使右表(表2
)没有匹配。右表无匹配时填充NULL
。
- 作用:返回左表(
语法:
SELECT 列
FROM 表1
LEFT JOIN 表2 ON 表1.列 = 表2.列;
示例:
返回左表中所有的记录 包括还没有分配部门的员工 查询所有员工及其所属部门(包括没有分配部门的员工)
SELECT e.name,e.email,d.department_name
FROM Employees e
LEFT JOIN Departments d ON e.department_id=d.department_id;
结果:所有员工(包括没有订单分配部门的)。
1.2.3. RIGHT JOIN
-
- 作用:返回右表(
表2
)的所有行,即使左表(表1
)没有匹配。左表无匹配时填充不显示
。
- 作用:返回右表(
语法:
SELECT 列
FROM 表1
LEFT JOIN 表2 ON 表1.列 = 表2.列;
示例:
--查询所有的部门,包括没有员工的部门
SELECT e.name, e.email, d.department_name
FROM Employees e
RIGHT JOIN Departments d ON e.department_id = d.department_id;
结果:所有部门(包括没有员工的)。
1.2.4. FULL JOIN
-
- SQLite 不直接支持
FULL JOIN
,但可以通过UNION
模拟实现。
- SQLite 不直接支持
语法:
SELECT 列
FROM 表1
LEFT JOIN 表2 ON 表1.列 = 表2.列;
UNION
SELECT 列
FROM 表1
RIGHT JOIN 表2 ON 表1.列 = 表2.列,
WHERE 列 IS NULL;
示例:
--查询所有结果 包括没有分配部门的员工与没有员工的部门
SELECT e.name, e.email, d.department_name
FROM Employees e
LEFT JOIN Departments d ON e.department_id = d.department_id
UNION
SELECT e.name, e.email, d.department_name
FROM Employees e
RIGHT JOIN Departments d ON e.department_id = d.department_id
WHERE e.name IS NULL;
结果:所有的员工与部门。
1.2.5. CROSS JOIN
-
- 作用:返回两个表的笛卡尔积(所有可能的行组合)。
语法:
SELECT 列
FROM 表1
CROSS JOIN 表2;
示例:
--CROSS JOIN 查询所有员工与所有部门的组合
SELECT e.name,e.email,d.department_name
FROM Employees e
CROSS JOIN Departments;
结果:每个客户与每个产品的组合(慎用,数据量可能极大),类似排列组合。
CROSS JOIN可以用ON如果用了ON 结果与INNER JOIN相同,但是正常并不会这样使用。
SELECT e.name,e.email,d.department_name
FROM Employees e
CROSS JOIN Departments d ON e.department_id=d.department_id;
1.2.6. NATURAL JOIN
-
- 作用:类似INNER JOIN 可以自动根据相同的列进行匹配,需要两个表有相同的列
语法:
SELECT 列
FROM 表1
NATURAL JOIN 表2;
示例:
--自动根据表中同名列进行拼接
SELECT e.name, e.email, d.department_name
FROM Employees e
NATURAL JOIN Departments d;
结果:
1.3. JOIN进阶操作
1.3.1. 多表联合查询
查询员工的姓名 email 部门 工资 最新通勤信息,奖惩信息(有则显示)请假信息(有则显示)
SELECT
e.name AS employee_name,
e.email,
d.department_name,
s.basic_salary,
s.bonus,
ap.description AS last_award_punishment,
ap.date AS last_award_punishment_date,
c.commute_date AS last_commute_date,
c.check_in_time AS last_check_in_time,
c.check_out_time AS last_check_out_time,
l.leave_start_date AS last_leave_start_date,
l.leave_end_date AS last_leave_end_date,
l.leave_reason AS last_leave_reason,
clb.remaining_hours AS compensatory_leave_balance
FROM Employees e
LEFT JOIN Departments d ON e.department_id = d.department_id
LEFT JOIN Salaries s ON e.employee_id = s.employee_id
LEFT JOIN AwardsPunishments ap ON e.employee_id = ap.employee_id
LEFT JOIN Commutes c ON e.employee_id = c.employee_id
LEFT JOIN Leaves l ON e.employee_id = l.employee_id
LEFT JOIN CompensatoryLeaveBalances clb ON e.employee_id = clb.employee_id
WHERE c.commute_date = (SELECT MAX(commute_date) FROM Commutes WHERE employee_id = e.employee_id);
结果
1.3.2. 使用子查询进行复杂条件过滤
假设我们需要查询在过去四年内获得过奖励的员工及其奖励信息。这可以通过子查询来实现。
1.3.2.1. 查询
sql
SELECT e.name AS employee_name,
e.email,
d.department_name,
ap.description AS award_description,
ap.date AS award_date
FROM Employees e
JOIN Departments d ON e.department_id=d.department_id
JOIN AwardsPunishments ap ON e.employee_id=ap.employee_id
WHERE ap.type='Award' AND ap.date >= strftime('%Y-%m-%d', 'now', '-4 year');
结果:
1.3.3. 性能优化:使用索引
为了提高查询性能,确保参与 JOIN
的字段上有索引。例如,employee_id
和 department_id
应该有索引。
创建索引:
CREATE INDEX idx_employee_id ON Employees(employee_id);
CREATE INDEX idx_department_id ON Departments(department_id);
1.3.4. 使用 JOIN
和 GROUP BY
进行数据汇总
假设我们需要查询每个部门的平均工资和最高工资。这可以通过 JOIN
和 GROUP BY
来实现。
查询:
SELECT
d.department_name,
AVG(s.basic_salary) AS average_salary,
MAX(s.basic_salary) AS max_salary
FROM Departments d
LEFT JOIN Employees e ON d.department_id = e.department_id
LEFT JOIN Salaries s ON e.employee_id = s.employee_id
GROUP BY d.department_name;
结果:
1.3.5. 使用 JOIN
和 HAVING
子句进行分组过滤
假设我们需要查询员工数量超过2人的部门。这可以通过 JOIN
和 HAVING
子句来实现。
查询:
SELECT
d.department_name,
COUNT(e.employee_id) AS employee_count
FROM Departments d
LEFT JOIN Employees e ON d.department_id = e.department_id
GROUP BY d.department_name
HAVING COUNT(e.employee_id) > 2;
结果:
1.3.6. 使用 JOIN
和 UNION
进行数据整合
假设我们需要查询所有获得过奖励的员工和所有请假的员工。这可以通过 JOIN
和 UNION
来实现。
查询:
SELECT
e.name AS employee_name,
e.email,
'Award' AS type,
ap.description AS description,
ap.date AS date
FROM Employees e
JOIN AwardsPunishments ap ON e.employee_id = ap.employee_id
WHERE ap.type = 'Award'
UNION
SELECT
e.name AS employee_name,
e.email,
'Leave' AS type,
l.leave_reason AS description,
l.leave_start_date AS date
FROM Employees e
JOIN Leaves l ON e.employee_id = l.employee_id;
结果:
1.4. 总结
通过这些高级用法,可以更灵活地使用 JOIN
来组合和查询多个表中的数据。JOIN
不仅可以用于简单的表连接,还可以结合子查询、WHERE
子句、ORDER BY
子句、GROUP BY
子句和 HAVING
子句来解决复杂的查询需求。