SQL JOIN和窗口函数是SQL查询中两个非常核心的功能,前者用于实现多表数据的关联拼接,后者用于在不改变原有结果集行数的前提下完成分组内的计算、排序等操作。在实际的数据分析场景中,单独使用其中一个往往无法满足复杂需求,将两者合理配合可以高效解决很多跨表统计、组内排名类的问题。

JOIN与窗口函数的基础概念
JOIN的作用
JOIN用于根据两个或多个表之间的关联字段,将分散在不同表中的数据拼接成一张完整的结果集。常见的JOIN类型包括INNER JOIN、LEFT JOIN、RIGHT JOIN等,不同的JOIN类型决定了结果集中包含哪些匹配或不匹配的数据行。
窗口函数的作用
窗口函数可以对结果集的一个子集(称为窗口)进行计算,计算时不会将多行合并成一行,而是为每一行返回一个对应的计算结果。常见的窗口函数包括排序类的ROW_NUMBER()、RANK(),聚合类的SUM() OVER()、AVG() OVER()等。
JOIN与窗口函数的配合逻辑
两者的配合通常分为两个步骤,第一步先用JOIN将需要的数据从多个表中关联出来,得到一个基础的结果集;第二步在这个基础结果集上应用窗口函数,完成分组内的计算或排序。需要注意的是,窗口函数的执行顺序在JOIN之后,因此窗口函数处理的是JOIN完成后的最终结果集。
常见配合场景与代码示例
场景一:跨表关联后做组内排名
需求:查询每个部门下员工的薪资排名,员工信息存在employee表,部门信息存在department表,需要通过dept_id关联两个表。
首先创建两张测试表并插入测试数据:
-- 创建部门表
CREATE TABLE department (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50)
);
-- 创建员工表
CREATE TABLE employee (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(50),
dept_id INT,
salary DECIMAL(10,2)
);
-- 插入部门测试数据
INSERT INTO department VALUES (1, '技术部');
INSERT INTO department VALUES (2, '市场部');
-- 插入员工测试数据
INSERT INTO employee VALUES (1, '张三', 1, 12000);
INSERT INTO employee VALUES (2, '李四', 1, 15000);
INSERT INTO employee VALUES (3, '王五', 1, 12000);
INSERT INTO employee VALUES (4, '赵六', 2, 10000);
INSERT INTO employee VALUES (5, '钱七', 2, 13000);
使用JOIN关联两张表,再用窗口函数做部门内的薪资排名:
SELECT
d.dept_name AS 部门名称,
e.emp_name AS 员工姓名,
e.salary AS 薪资,
-- 按部门分组,组内按薪资降序排名
RANK() OVER (PARTITION BY d.dept_id ORDER BY e.salary DESC) AS 部门薪资排名
FROM employee e
INNER JOIN department d ON e.dept_id = d.dept_id;
执行上述查询后,会得到每个部门下员工的薪资排名,相同薪资的员工排名相同,下一个排名会跳过重复的位数。
场景二:关联后计算组内累计值
需求:查询每个员工的薪资,以及所在部门的薪资累计总和,同样需要关联员工表和部门表。
基于上面的测试表,使用以下查询实现:
SELECT
d.dept_name AS 部门名称,
e.emp_name AS 员工姓名,
e.salary AS 薪资,
-- 按部门分组,组内按员工ID排序,计算薪资累计和
SUM(e.salary) OVER (PARTITION BY d.dept_id ORDER BY e.emp_id) AS 部门累计薪资
FROM employee e
INNER JOIN department d ON e.dept_id = d.dept_id;
这个查询会先通过JOIN拿到每个员工对应的部门名称,然后窗口函数会按部门分组,组内按员工ID排序,逐步累加薪资得到累计值。
场景三:LEFT JOIN后处理缺失数据的窗口计算
需求:查询所有部门的信息,以及每个部门下的最高薪资,如果部门没有员工则最高薪资显示为NULL。
使用LEFT JOIN关联,再配合窗口函数的最大值计算:
SELECT
d.dept_name AS 部门名称,
e.emp_name AS 员工姓名,
e.salary AS 薪资,
-- 按部门分组,计算组内最高薪资
MAX(e.salary) OVER (PARTITION BY d.dept_id) AS 部门最高薪资
FROM department d
LEFT JOIN employee e ON d.dept_id = e.dept_id;
这里使用LEFT JOIN保证所有部门都会出现在结果集中,即使部门没有员工,窗口函数也会为该部门的所有行返回NULL作为最高薪资。
配合使用的注意事项
- JOIN的执行顺序在窗口函数之前,因此窗口函数的PARTITION BY和ORDER BY只能使用JOIN之后结果集中存在的字段。
- 如果JOIN之后结果集的行数较多,窗口函数的计算可能会消耗较多资源,需要合理设计索引提升查询效率。
- 当使用LEFT JOIN等可能返回NULL值的关联方式时,窗口函数处理NULL值的逻辑需要和实际需求匹配,比如可以用COALESCE函数转换NULL值。
总结
SQL JOIN和窗口函数的配合是处理复杂查询的有效方式,JOIN负责整合多表数据,窗口函数负责在整合后的数据上做精细化计算。只要掌握先关联再计算的逻辑,就能应对大部分跨表分组统计、排名类的需求,减少复杂子查询的使用,让SQL代码更简洁易读。