一文搞懂 SQL 窗口函数 – wiki基地


一文搞懂 SQL 窗口函数:告别子查询和临时表,解锁高级数据分析的利器

在 SQL 的世界里,我们经常需要对数据进行聚合计算,比如计算总和、平均值、最大最小值等。最常用的工具是 GROUP BY 子句,它可以将数据按某个或多个列分组,然后对每个组进行聚合。然而,GROUP BY 的一个固有局限性是,它会将原始行合并成组,我们无法在同一查询结果中既看到聚合值,又保留原始的明细行数据。

想象一下这样的场景:

  • 你想计算每个部门的平均工资, 同时 显示每个员工的工资以及他们与部门平均工资的差距。
  • 你想计算一个用户历史订单的总金额, 同时 列出用户的每一笔订单。
  • 你想对每个销售区域内的销售额进行排名, 同时 保留每一条销售记录。
  • 你想计算每天的销售额以及前一天的销售额,以便分析趋势或计算环比增长。
  • 你想计算一个用户从注册至今的累计消费金额。

这些任务用传统的 GROUP BY 很难甚至不可能在一个查询中优雅地完成。你可能需要使用子查询、临时表、甚至多次查询,这会让 SQL 代码变得复杂、难以阅读,并且可能影响性能。

幸运的是,SQL 提供了一个强大的功能来解决这些问题——窗口函数(Window Functions)。窗口函数在某种程度上类似于聚合函数,但它们的独特之处在于,它们计算的是与当前行“相关”的一组行(一个“窗口”)的数据,并且不会GROUP BY 那样将行合并。这意味着,使用窗口函数,你可以在保留原始行明细的同时,为每一行添加一个基于窗口计算出的值。

本文将带你深入了解 SQL 窗口函数,从基本概念到高级用法,助你彻底掌握这一数据分析利器。

什么是 SQL 窗口函数?

简单来说,窗口函数是在与当前查询行相关的一组行上执行计算的函数。这组行被称为“窗口”。

与普通聚合函数的区别:

  1. 不合并行: 窗口函数不会减少输出的行数。输出结果集的行数与输入结果集的行数相同。
  2. 针对窗口计算: 计算是针对窗口内的行进行的,而不是整个结果集或 GROUP BY 分组。
  3. 可以在 SELECTORDER BY 子句中使用: 这使得我们可以在选取数据的同时进行复杂的分析计算。

窗口函数的通用语法如下:

sql
window_function(arg1, arg2, ...) OVER ([PARTITION BY column1, column2, ...] [ORDER BY column3 ASC/DESC, ...] [frame_clause])

让我们分解一下这个语法中的各个组成部分:

  • window_function(arg1, arg2, ...): 这是具体的窗口函数名称,比如 SUM()AVG()ROW_NUMBER()RANK()LAG()LEAD() 等。它可以接受参数,就像普通函数一样。
  • OVER(): 这个关键字是窗口函数的标志。它告诉数据库,前面的函数是一个窗口函数,而不是一个普通的聚合函数或标量函数。
  • OVER() 括号内的部分定义了“窗口”:
    • [PARTITION BY column1, column2, ...]分区子句。 它将结果集划分为独立的、互不影响的区域(分区)。窗口函数在每个分区内独立计算。这非常类似于 GROUP BY 的分组概念,但它不合并行。如果没有 PARTITION BY 子句,整个结果集将被视为一个单一的分区。
    • [ORDER BY column3 ASC/DESC, ...]排序子句。 它定义了在每个分区内(或整个结果集内,如果没有 PARTITION BY)行的顺序。对于某些窗口函数(如排名函数、顺序敏感的聚合函数如累计和、以及 LAG/LEAD 函数),ORDER BY 子句是必需的或非常重要的。它决定了窗口函数在处理行时的逻辑顺序。
    • [frame_clause]窗口帧子句。 这是窗口函数中最灵活(也可能是最复杂)的部分。它定义了当前行的“窗口”的具体范围。这个范围是基于 ORDER BY 子句定义的顺序来确定的。默认的窗口帧取决于是否有 ORDER BY 子句。

OVER() 子句的组成部分详解

理解 OVER() 子句的各个部分是掌握窗口函数的关键。

1. OVER() – 窗口函数标识符

这是最基础的部分。只要在函数后面加上 OVER(),它就变成了一个窗口函数。

示例:

计算所有员工的平均工资,并将这个平均值显示在每一行员工记录旁边。

假设我们有一个 Employees 表:

EmployeeID Name Department Salary
1 Alice Sales 50000
2 Bob IT 60000
3 Charlie Sales 55000
4 David IT 62000
5 Eve Sales 52000

sql
SELECT
EmployeeID,
Name,
Department,
Salary,
AVG(Salary) OVER() AS TotalAverageSalary
FROM
Employees;

结果:

EmployeeID Name Department Salary TotalAverageSalary
1 Alice Sales 50000 55800.00
2 Bob IT 60000 55800.00
3 Charlie Sales 55000 55800.00
4 David IT 62000 55800.00
5 Eve Sales 52000 55800.00

可以看到,AVG(Salary) OVER() 计算了整个结果集的平均工资(50000+60000+55000+62000+52000 / 5 = 55800),并将这个值复制到了每一行。这里 OVER() 是空的,表示窗口是整个结果集。

2. PARTITION BY – 划分窗口

PARTITION BY 将结果集切分成独立的分区,窗口函数在每个分区内独立操作。

示例:

计算每个部门的平均工资,并将这个部门平均值显示在每个员工记录旁边。

sql
SELECT
EmployeeID,
Name,
Department,
Salary,
AVG(Salary) OVER(PARTITION BY Department) AS DepartmentAverageSalary
FROM
Employees;

结果:

EmployeeID Name Department Salary DepartmentAverageSalary
1 Alice Sales 50000 52333.33
3 Charlie Sales 55000 52333.33
5 Eve Sales 52000 52333.33
2 Bob IT 60000 61000.00
4 David IT 62000 61000.00

这里 PARTITION BY Department 将数据按部门分成了两组(Sales和IT)。AVG(Salary) 窗口函数在 Sales 分区内计算平均值 (50000+55000+52000 / 3 = 52333.33),并在 Sales 部门的每一行显示这个值。在 IT 分区内独立计算平均值 (60000+62000 / 2 = 61000),并在 IT 部门的每一行显示这个值。

这是 PARTITION BYGROUP BY 的核心区别:GROUP BY 会把 Sales 的三行合并成一行,IT 的两行合并成一行,最终输出两行;而使用窗口函数,原始的五行记录都被保留了下来。

3. ORDER BY – 定义窗口内的顺序

ORDER BY 子句定义了窗口内行的处理顺序。这对于需要顺序敏感的计算(如排名、累计、前后行比较)至关重要。

示例:

计算每个部门内员工的工资排名。

sql
SELECT
EmployeeID,
Name,
Department,
Salary,
RANK() OVER(PARTITION BY Department ORDER BY Salary DESC) AS DepartmentRank
FROM
Employees;

结果:

EmployeeID Name Department Salary DepartmentRank
3 Charlie Sales 55000 1
5 Eve Sales 52000 2
1 Alice Sales 50000 3
4 David IT 62000 1
2 Bob IT 60000 2

这里,PARTITION BY Department 先将数据按部门分区。ORDER BY Salary DESC 在每个部门内部按工资降序排列。RANK() 函数则根据这个顺序在每个分区内生成排名。Charlie 在 Sales 部门工资最高,排名1;David 在 IT 部门工资最高,排名1。

4. frame_clause – 窗口帧

窗口帧子句进一步细化了窗口的范围,它决定了在当前行计算时,哪些行会被包含进来。窗口帧是基于 ORDER BY 子句定义的顺序来确定的。它通常用于聚合类的窗口函数(如 SUM, AVG, COUNT, MIN, MAX),以便计算移动平均、累计总和等。

窗口帧的语法通常是 RANGE BETWEEN ... AND ...ROWS BETWEEN ... AND ...

  • ROWS 基于物理行的偏移量。
  • RANGE 基于当前行 ORDER BY 列的值的偏移量。

常见的帧定义关键词:

  • UNBOUNDED PRECEDING: 从分区的开始到当前行。
  • CURRENT ROW: 当前行本身。
  • UNBOUNDED FOLLOWING: 从当前行到分区的结束。
  • <N> PRECEDING: 当前行之前的 N 行。
  • <N> FOLLOWING: 当前行之后的 N 行。

默认窗口帧:

  • 如果 OVER() 子句包含 ORDER BY,则默认帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这意味着窗口从分区开始到当前行,基于 ORDER BY 列的值范围。对于相同的 ORDER BY 值,所有行都被包含在内。
  • 如果 OVER() 子句没有 ORDER BY,则默认帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这意味着窗口是整个分区(或整个结果集如果没有 PARTITION BY),这也就是前面 AVG() OVER()AVG() OVER(PARTITION BY Department) 示例中的默认行为。

理解默认帧非常重要,尤其是在计算累计值时。

示例:计算员工的累计工资(按部门和工资排序):

sql
SELECT
EmployeeID,
Name,
Department,
Salary,
-- 默认帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
-- 在ORDER BY Salary DESC时,对同一个Salary值会包含所有相同值之前的和它们自身
SUM(Salary) OVER(PARTITION BY Department ORDER BY Salary DESC) AS CumulativeSalary_DefaultFrame
FROM
Employees;

结果:

EmployeeID Name Department Salary CumulativeSalary_DefaultFrame
3 Charlie Sales 55000 55000
5 Eve Sales 52000 107000
1 Alice Sales 50000 157000
4 David IT 62000 62000
2 Bob IT 60000 122000

在这个例子中,由于 ORDER BY Salary DESC,默认帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

  • Charlie (Sales, 55000): 窗口包含 Sales 分区中工资 >= 55000 的行。只有她自己。累计 55000。
  • Eve (Sales, 52000): 窗口包含 Sales 分区中工资 >= 52000 的行 (Charlie, Eve)。累计 55000 + 52000 = 107000。
  • Alice (Sales, 50000): 窗口包含 Sales 分区中工资 >= 50000 的行 (Charlie, Eve, Alice)。累计 55000 + 52000 + 50000 = 157000。

如果希望累计是严格基于行的物理顺序(在 ORDER BY 下),可以使用 ROWS 帧:

示例:计算基于行的累计工资:

sql
SELECT
EmployeeID,
Name,
Department,
Salary,
SUM(Salary) OVER(PARTITION BY Department ORDER BY Salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CumulativeSalary_RowsFrame
FROM
Employees;

结果:

EmployeeID Name Department Salary CumulativeSalary_RowsFrame
3 Charlie Sales 55000 55000
5 Eve Sales 52000 107000
1 Alice Sales 50000 157000
4 David IT 62000 62000
2 Bob IT 60000 122000

在这个特定的数据和排序下,RANGEROWS 的默认行为产生了相同的结果,因为没有工资完全相同的员工。但在有相同 ORDER BY 值(tie)的情况下,它们可能会产生不同的累计结果。

示例:计算基于行的 2 行移动平均(当前行及前 1 行):

“`sql
— 假设有一个包含日期和销售额的 Sales 表
— SalesDate | Amount
— ———-|——-
— 2023-01-01| 100
— 2023-01-02| 150
— 2023-01-03| 120
— 2023-01-04| 200
— 2023-01-05| 180

SELECT
SalesDate,
Amount,
AVG(Amount) OVER(ORDER BY SalesDate ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS TwoDayMovingAverage
FROM
Sales;
“`

结果(示例):

SalesDate Amount TwoDayMovingAverage
2023-01-01 100 100.00
2023-01-02 150 125.00
2023-01-03 120 135.00
2023-01-04 200 160.00
2023-01-05 180 190.00

ROWS BETWEEN 1 PRECEDING AND CURRENT ROW 定义了窗口帧包含当前行以及紧挨着它的前一行(根据 ORDER BY SalesDate 的顺序)。

常见的窗口函数类型

窗口函数大致可以分为几类:

  1. 聚合函数 (Aggregate Functions): 在窗口内执行聚合计算。
  2. 排名函数 (Ranking Functions): 在窗口内为每一行生成排名。
  3. 值函数 (Value Functions): 获取窗口内特定位置或偏移量的行的值。

让我们详细看看这些函数。

1. 聚合函数作为窗口函数

几乎所有的标准聚合函数 (SUM, AVG, COUNT, MIN, MAX) 都可以用作窗口函数,只需要在它们后面加上 OVER() 子句。

语法: AGG_FUNCTION(column) OVER ([PARTITION BY ...] [ORDER BY ...] [frame_clause])

示例:

  • 部门总销售额 (同时显示明细):
    sql
    SELECT
    OrderID,
    Region,
    Amount,
    SUM(Amount) OVER(PARTITION BY Region) AS TotalSalesPerRegion
    FROM
    Orders;
  • 按日期累计销售额:
    sql
    SELECT
    OrderDate,
    Amount,
    SUM(Amount) OVER(ORDER BY OrderDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CumulativeSales
    FROM
    DailySales;
  • 过去 7 天滚动平均销售额:
    sql
    SELECT
    OrderDate,
    Amount,
    AVG(Amount) OVER(ORDER BY OrderDate ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS SevenDayMovingAvg
    FROM
    DailySales;

2. 排名函数

排名函数用于在窗口内为每一行生成一个排名。它们通常需要 ORDER BY 子句来确定排名依据。

  • ROW_NUMBER(): 为分区内的每一行分配一个唯一的、连续的整数。排名不考虑并列。
    sql
    -- 给每个部门工资从高到低编号
    SELECT
    Name,
    Department,
    Salary,
    ROW_NUMBER() OVER(PARTITION BY Department ORDER BY Salary DESC) AS RowNum
    FROM
    Employees;

    结果(示例):
    | Name | Department | Salary | RowNum |
    | :—— | :——— | :—– | :—– |
    | Charlie | Sales | 55000 | 1 |
    | Eve | Sales | 52000 | 2 |
    | Alice | Sales | 50000 | 3 |
    | David | IT | 62000 | 1 |
    | Bob | IT | 60000 | 2 |

  • RANK(): 为分区内的每一行分配一个排名。如果存在并列,并列的行会获得相同的排名,下一个排名会跳过相应数量的位置。
    sql
    -- 给每个部门工资从高到低排名 (有跳跃)
    SELECT
    Name,
    Department,
    Salary,
    RANK() OVER(PARTITION BY Department ORDER BY Salary DESC) AS RankNum
    FROM
    Employees;

    结果(示例,假设Charlie和Eve工资相同):
    | Name | Department | Salary | RankNum |
    | :—— | :——— | :—– | :—— |
    | Charlie | Sales | 55000 | 1 |
    | Eve | Sales | 55000 | 1 |
    | Alice | Sales | 50000 | 3 | — 跳过了排名 2
    | … | … | … | … |

  • DENSE_RANK(): 为分区内的每一行分配一个排名。如果存在并列,并列的行会获得相同的排名,下一个排名是紧挨着的下一个整数,不会跳过位置。
    sql
    -- 给每个部门工资从高到低排名 (无跳跃)
    SELECT
    Name,
    Department,
    Salary,
    DENSE_RANK() OVER(PARTITION BY Department ORDER BY Salary DESC) AS DenseRankNum
    FROM
    Employees;

    结果(示例,假设Charlie和Eve工资相同):
    | Name | Department | Salary | DenseRankNum |
    | :—— | :——— | :—– | :———– |
    | Charlie | Sales | 55000 | 1 |
    | Eve | Sales | 55000 | 1 |
    | Alice | Sales | 50000 | 2 | — 没有跳过排名 2
    | … | … | … | … |

  • NTILE(n): 将分区内的行尝试平均分成 n 个组,并为每一行分配一个组号(从 1 到 n)。
    sql
    -- 将所有员工按工资分成 3 组
    SELECT
    Name,
    Salary,
    NTILE(3) OVER(ORDER BY Salary) AS SalaryGroup
    FROM
    Employees;

    结果(示例):
    | Name | Salary | SalaryGroup |
    | :—— | :—– | :———- |
    | Alice | 50000 | 1 |
    | Eve | 52000 | 1 |
    | Charlie | 55000 | 2 |
    | Bob | 60000 | 2 |
    | David | 62000 | 3 |

3. 值函数

值函数用于访问窗口内相对于当前行的其他行的值。它们非常适合进行前后行比较、获取分区内的第一行/最后一行等操作。它们通常需要 ORDER BY 子句。

  • LAG(column, offset, default): 获取当前行之前offset 行的 column 值。如果 offset 处的行不存在,返回 default 值(如果指定),否则返回 NULLoffset 默认为 1。
    sql
    -- 计算员工工资与部门内上一位(按工资排序)员工的工资差
    SELECT
    Name,
    Department,
    Salary,
    LAG(Salary, 1, 0) OVER(PARTITION BY Department ORDER BY Salary) AS PreviousSalary, -- 没有前一位时返回 0
    Salary - LAG(Salary, 1, Salary) OVER(PARTITION BY Department ORDER BY Salary) AS SalaryDifference -- 没有前一位时差值为 0
    FROM
    Employees;

    结果(示例):
    | Name | Department | Salary | PreviousSalary | SalaryDifference |
    | :—— | :——— | :—– | :————- | :————— |
    | Alice | Sales | 50000 | 0 | 0 |
    | Eve | Sales | 52000 | 50000 | 2000 |
    | Charlie | Sales | 55000 | 52000 | 3000 |
    | Bob | IT | 60000 | 0 | 0 |
    | David | IT | 62000 | 60000 | 2000 |

  • LEAD(column, offset, default): 获取当前行之后offset 行的 column 值。如果 offset 处的行不存在,返回 default 值(如果指定),否则返回 NULLoffset 默认为 1。
    sql
    -- 获取员工部门内下一位(按工资排序)员工的工资
    SELECT
    Name,
    Department,
    Salary,
    LEAD(Salary, 1, 0) OVER(PARTITION BY Department ORDER BY Salary) AS NextSalary
    FROM
    Employees;

    结果(示例):
    | Name | Department | Salary | NextSalary |
    | :—— | :——— | :—– | :——— |
    | Alice | Sales | 50000 | 52000 |
    | Eve | Sales | 52000 | 55000 |
    | Charlie | Sales | 55000 | 0 |
    | Bob | IT | 60000 | 62000 |
    | David | IT | 62000 | 0 |

  • FIRST_VALUE(column): 获取窗口帧内第一行的 column 值。
    sql
    -- 获取每个部门工资最低(排序第一)员工的工资
    SELECT
    Name,
    Department,
    Salary,
    FIRST_VALUE(Salary) OVER(PARTITION BY Department ORDER BY Salary) AS LowestSalaryInDept
    FROM
    Employees;

    结果(示例):
    | Name | Department | Salary | LowestSalaryInDept |
    | :—— | :——— | :—– | :—————– |
    | Alice | Sales | 50000 | 50000 |
    | Eve | Sales | 52000 | 50000 |
    | Charlie | Sales | 55000 | 50000 |
    | Bob | IT | 60000 | 60000 |
    | David | IT | 62000 | 60000 |
    注意: FIRST_VALUELAST_VALUE 受窗口帧影响。默认帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,所以 FIRST_VALUE 通常表现良好(取分区开始的第一行)。但 LAST_VALUE 在默认帧下通常只取到当前行,可能不是你想要的。如果你想取到分区 真正 的最后一行,需要明确指定帧为 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 或类似能包含最后一行的帧。

  • LAST_VALUE(column): 获取窗口帧内最后一行的 column 值。
    sql
    -- 尝试获取每个部门工资最高(排序最后)员工的工资
    SELECT
    Name,
    Department,
    Salary,
    -- 使用明确的帧来确保窗口包含分区最后一行
    LAST_VALUE(Salary) OVER(PARTITION BY Department ORDER BY Salary ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS HighestSalaryInDept
    FROM
    Employees;

    结果(示例):
    | Name | Department | Salary | HighestSalaryInDept |
    | :—— | :——— | :—– | :—————— |
    | Alice | Sales | 50000 | 55000 |
    | Eve | Sales | 52000 | 55000 |
    | Charlie | Sales | 55000 | 55000 |
    | Bob | IT | 60000 | 62000 |
    | David | IT | 62000 | 62000 |

  • NTH_VALUE(column, n): 获取窗口帧内第 n 行的 column 值(基于 ORDER BY 顺序)。
    sql
    -- 获取每个部门工资第二高员工的工资 (可能需要处理并列)
    SELECT
    Name,
    Department,
    Salary,
    -- 使用明确的帧确保窗口包含足够多的行
    NTH_VALUE(Salary, 2) OVER(PARTITION BY Department ORDER BY Salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS SecondHighestSalaryInDept
    FROM
    Employees;

    结果(示例):
    | Name | Department | Salary | SecondHighestSalaryInDept |
    | :—— | :——— | :—– | :———————— |
    | Charlie | Sales | 55000 | 52000 |
    | Eve | Sales | 52000 | 52000 |
    | Alice | Sales | 50000 | 52000 |
    | David | IT | 62000 | 60000 |
    | Bob | IT | 60000 | 60000 |

窗口函数与 GROUP BY 的根本区别再回顾

这是初学者最容易混淆的地方,再次强调:

  • GROUP BY 用于将行聚合成组,并为每个组生成一个输出行。它减少了结果集的行数。聚合函数(如 SUM, AVG)在每个组上计算,结果是每个组的一个值。
  • 窗口函数: 在一个与当前行相关的“窗口”上执行计算,但合并行。它为输入结果集的每一行生成一个输出值。窗口函数的计算可以基于整个结果集、一个分区或一个窗口帧。

何时使用哪个?

  • 如果你需要一个结果集,其中每一行代表一个组的汇总信息(例如,每个部门的总销售额,不需要看到每个订单),使用 GROUP BY
  • 如果你需要在原始的明细行旁边显示一些聚合或排名信息(例如,每个订单旁边显示该订单所属区域的总销售额,或者每个员工旁边显示他在部门内的排名),使用窗口函数。

当然,在复杂的查询中,你可以结合使用它们。例如,你可能先用 GROUP BY 计算出一些中间聚合结果,然后在这些聚合结果上使用窗口函数进行进一步分析。或者,你可以在使用窗口函数后,在一个外部查询中使用 WHERE 子句(或者更常见的,将其放入 CTE 或子查询,然后在外部查询中筛选)来过滤基于窗口函数结果的行(比如找出每个部门工资最高的员工,这需要先排名再筛选)。

窗口函数的应用场景

窗口函数在数据分析领域有着广泛的应用,包括但不限于:

  • 排名和分级: 计算销售额、分数、排名的前 N 名。
  • 累计和和移动平均: 分析随时间变化的趋势,如累计销售额、滚动平均值。
  • 前后行比较: 计算环比增长、差异、间隔时间。
  • 组内百分比: 计算某个值占其所属分组总和或平均值的百分比。
  • 填充缺失值: 使用 LAGLEAD 等函数填充基于序列的缺失值。
  • 复杂过滤: 虽然不能直接在 WHERE 中使用窗口函数,但可以通过子查询或 CTE 先计算出窗口函数结果,然后在外部查询中过滤。例如,找出每个部门销售额最高的员工:

    sql
    WITH RankedEmployees AS (
    SELECT
    EmployeeID,
    Name,
    Department,
    Salary,
    RANK() OVER(PARTITION BY Department ORDER BY Salary DESC) as rn
    FROM Employees
    )
    SELECT
    EmployeeID,
    Name,
    Department,
    Salary
    FROM RankedEmployees
    WHERE rn = 1;

窗口函数的性能考虑

窗口函数虽然强大,但计算成本可能比简单的聚合或过滤更高。数据库需要为每一行确定其所属的窗口,并在该窗口上执行计算。PARTITION BYORDER BY 子句中涉及的列通常需要数据库进行排序和分组操作。

性能建议:

  • 索引:PARTITION BYORDER BY 子句中使用的列上建立索引通常有助于提高性能。
  • 限制数据量: 在使用窗口函数之前,尽可能先使用 WHERE 子句过滤掉不需要的行,减少参与窗口计算的数据量。
  • 避免不必要的复杂性: 只有在确实需要窗口函数时才使用它。简单的聚合使用 GROUP BY 更高效。
  • 理解窗口帧: 精确定义窗口帧可以避免对不必要的行进行计算。

总结

SQL 窗口函数是进行高级数据分析的强大工具。它们允许你在不合并原始数据行的情况下,对相关行集执行计算。通过掌握 OVER() 子句中的 PARTITION BYORDER BY 和窗口帧(ROWS/RANGE BETWEEN),你可以执行排名、累计计算、前后行比较等复杂的分析任务,从而写出更简洁、更高效、更易读的 SQL 代码。

从聚合函数到排名函数再到值函数,每种类型的窗口函数都有其独特的用途。理解它们的工作原理以及如何通过 OVER() 子句控制它们的行为,将极大地提升你的 SQL 数据处理能力。

不要被最初的复杂性吓倒,窗口函数值得投入时间学习。从简单的例子开始练习,逐步尝试更复杂的场景,你将很快能驾驭这一强大的 SQL 特性,解锁更深层次的数据洞察。

现在,你已经具备了理解 SQL 窗口函数所需的所有基本知识。勇敢地在你的查询中实践它们吧!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部