1. 什么是开窗函数
在数据库查询中,开窗函数(Window Function)是一种特殊的函数,它可以在查询结果中创建一个窗口(Window),并在这个窗口上执行一系列聚合计算或排序操作。与其他聚合函数或排序函数不同的是,开窗函数可以处理整个查询结果集中的数据,而不仅仅是聚合计算或排序操作的结果。因此,开窗函数非常有用,可以帮助我们更加高效地进行数据分析和统计。
1.1 开窗函数的语法
在 MSSQL 中,开窗函数的语法大致如下:
SELECT [DISTINCT] select_list,
window_function OVER (
[PARTITION BY partition_expression]
[ORDER BY order_expression [ASC | DESC] [,order_expression [ASC | DESC] ...]]
[ROWS frame_clause]
) AS window_alias
FROM table_reference
[WHERE where_expression]
[GROUP BY group_by_expression]
[HAVING having_expression];
其中,window_function 表示要执行的窗口函数,partition_expression 用于指定分区条件,order_expression 用于指定排序方式,frame_clause 用于指定窗口帧大小和位置,window_alias 表示窗口函数的别名。
1.2 开窗函数的常用类型
MSSQL 中常用的开窗函数包括:
ROW_NUMBER:给每一行数据赋值一个序号
RANK:对数据进行排名,相同数值的行具有相同的排名,但跳过相同的排名
DENSE_RANK:对数据进行排名,相同数值的行具有相同的排名,但DENSE_RANK()
不跳过相同的排名
NTILE:将数据分为若干组,并对每组分配一个编号
LEAD:返回指定列当前行之后的第 n 行的值
LAG:返回指定列当前行之前的第 n 行的值
2. 实例演示
下面,我们通过一个实例来演示如何使用开窗函数。
2.1 示例数据
假设我们有一张订单表 order_info,包含如下字段:
order_id:订单编号
order_date:订单日期
customer_name:客户姓名
order_amount:订单金额
我们通过以下 SQL 语句创建订单表并插入一些示例数据:
CREATE TABLE order_info (
order_id INT PRIMARY KEY,
order_date DATE,
customer_name VARCHAR(100),
order_amount DECIMAL(18, 2)
);
INSERT INTO order_info VALUES (1, '2021-01-01', '张三', 100.00);
INSERT INTO order_info VALUES (2, '2021-01-02', '李四', 200.00);
INSERT INTO order_info VALUES (3, '2021-01-03', '王五', 300.00);
INSERT INTO order_info VALUES (4, '2021-01-04', '赵六', 400.00);
2.2 使用 ROW_NUMBER
我们可以通过 ROW_NUMBER() 函数为每个订单生成一个序号,如下:
SELECT order_id, order_date, customer_name, order_amount,
ROW_NUMBER() OVER (PARTITION BY customer_name ORDER BY order_amount DESC) AS order_rank
FROM order_info;
这条 SQL 语句的含义是:先根据客户姓名对订单进行分组,然后按订单金额从大到小排序,最后为每个订单分配一个序号。在这个示例中,我们使用了 PARTITION BY 子句来指定分组条件,使用 ORDER BY 子句来指定排序方式,使用 ROW_NUMBER() 函数来生成序号。执行以上 SQL 语句,得到的结果如下:
| order_id | order_date | customer_name | order_amount | order_rank |
|----------|------------|---------------|--------------|------------|
| 3 | 2021-01-03 | 王五 | 300.00 | 1 |
| 4 | 2021-01-04 | 赵六 | 400.00 | 1 |
| 2 | 2021-01-02 | 李四 | 200.00 | 1 |
| 1 | 2021-01-01 | 张三 | 100.00 | 1 |
2.3 使用 RANK
接下来,我们使用 RANK() 函数对订单进行排名。相同金额的订单具有相同的排名,但排名号会跳过相同的排名,如下:
SELECT order_id, order_date, customer_name, order_amount,
RANK() OVER (PARTITION BY customer_name ORDER BY order_amount DESC) AS order_rank
FROM order_info;
此时,我们使用了 RANK() 函数来进行排名。执行以上 SQL 语句,得到的结果如下:
| order_id | order_date | customer_name | order_amount | order_rank |
|----------|------------|---------------|--------------|------------|
| 3 | 2021-01-03 | 王五 | 300.00 | 1 |
| 4 | 2021-01-04 | 赵六 | 400.00 | 1 |
| 2 | 2021-01-02 | 李四 | 200.00 | 1 |
| 1 | 2021-01-01 | 张三 | 100.00 | 1 |
2.4 使用 DENSE_RANK
我们还可以使用 DENSE_RANK() 函数进行排名。相同金额的订单具有相同的排名,但排名号不会跳过相同的排名,如下:
SELECT order_id, order_date, customer_name, order_amount,
DENSE_RANK() OVER (PARTITION BY customer_name ORDER BY order_amount DESC) AS order_rank
FROM order_info;
此时,我们使用了 DENSE_RANK() 函数来进行排名。执行以上 SQL 语句,得到的结果如下:
| order_id | order_date | customer_name | order_amount | order_rank |
|----------|------------|---------------|--------------|------------|
| 3 | 2021-01-03 | 王五 | 300.00 | 1 |
| 4 | 2021-01-04 | 赵六 | 400.00 | 2 |
| 2 | 2021-01-02 | 李四 | 200.00 | 1 |
| 1 | 2021-01-01 | 张三 | 100.00 | 1 |
2.5 使用 NTILE
我们还可以使用 NTILE() 函数将订单分为若干组,并对每组分配一个编号,如下:
SELECT order_id, order_date, customer_name, order_amount,
NTILE(2) OVER (PARTITION BY customer_name ORDER BY order_amount DESC) AS order_group
FROM order_info;
此时,我们使用了 NTILE() 函数将订单分为了两组。执行以上 SQL 语句,得到的结果如下:
| order_id | order_date | customer_name | order_amount | order_group |
|----------|------------|---------------|--------------|--------------|
| 3 | 2021-01-03 | 王五 | 300.00 | 1 |
| 4 | 2021-01-04 | 赵六 | 400.00 | 2 |
| 2 | 2021-01-02 | 李四 | 200.00 | 1 |
| 1 | 2021-01-01 | 张三 | 100.00 | 1 |
2.6 使用 LEAD 和 LAG
最后,我们可以使用 LEAD() 或 LAG() 函数查找指定列当前行前后的值。例如,我们可以使用 LEAD() 函数查找当前行后面的第一条订单记录,如下:
SELECT order_id, order_date, customer_name, order_amount,
LEAD(order_amount) OVER (PARTITION BY customer_name ORDER BY order_id) AS next_order_amount
FROM order_info;
在这个示例代码中,我们使用了 LEAD(order_amount) 函数来查找当前行后面的订单金额。执行以上 SQL 语句,得到的结果如下:
| order_id | order_date | customer_name | order_amount | next_order_amount |
|----------|------------|---------------|--------------|---------------------|
| 1 | 2021-01-01 | 张三 | 100.00 | 200.00 |
| 2 | 2021-01-02 | 李四 | 200.00 | 300.00 |
| 3 | 2021-01-03 | 王五 | 300.00 | 400.00 |
| 4 | 2021-01-04 | 赵六 | 400.00 | NULL |
可以看到,每个客户的最后一条记录的下一条记录为空。与 LEAD() 函数类似,我们也可以使用 LAG() 函数来查找当前行前面的第一条订单记录。
3. 总结
开窗函数是 MSSQL 查询中的一个强大工具,可以帮助我们更加高效地进行数据分析和统计。在实际工作中,我们可以根据需要选择不同的开窗函数,来满足不同的数据分析需求。同时,我们也需要注意使用开窗函数对性能的影响,避免查询执行过慢或占用过多系统资源。