问题-分组查询最新一条

昨天遇到这样一个问题,在数据表A中存在字段:id (primary key),ref_id,ref_name,created;
想在A表中对ref_id进行分组查询,并获取到每个分组中最新的那一条,怎么办?

这里想到了用IN字句来去做:

SELECT id, ref_id, ref_name, created
FROM A
WHERE (ref_id, created) IN (
SELECT ref_id, MAX(created)
FROM A
GROUP BY ref_id
);

但是这样的处理,效率非常低,因为它会在IN子句中进行多次查询匹配。查询资料后得到如下介绍:

窗口函数

窗口函数(Window Function)是一种在查询结果集中的每一行上计算值的函数。它能够根据指定的窗口范围,对每一行进行排序、分组和计算,而不会修改查询结果本身。窗口函数通常与OVER子句一起使用。

窗口函数的一般语法如下:

<窗口函数>([参数]) OVER (
[PARTITION BY <分组列>]
[ORDER BY <排序列> [ASC|DESC]]
[ROWS <窗口范围>]
)

下面是对窗口函数语法中的各个部分进行详细解释:

  • <窗口函数>:表示要使用的窗口函数,如SUM、COUNT、ROW_NUMBER等。窗口函数可以是聚合函数(如SUM、COUNT、AVG),也可以是排名函数(如ROW_NUMBER、RANK、DENSE_RANK),还可以是其他类型的函数。可以根据具体需求选择合适的窗口函数。
  • [参数]:是可选的,表示窗口函数的参数。参数可以是列名、常量或其他表达式,根据具体的窗口函数而定。
  • OVER:是关键字,用于引入窗口函数的定义。
  • [PARTITION BY <分组列>]:是可选的,用于指定分组的列。如果指定了分组列,窗口函数将在每个分组内进行计算和排序。不同分组内的计算是相互独立的。
  • [ORDER BY <排序列> [ASC|DESC]]:是可选的,用于指定排序的列和排序的顺序。窗口函数将根据指定的排序列对每一行进行排序。ASC表示升序(默认),DESC表示降序。
  • [ROWS <窗口范围>]:是可选的,用于指定窗口的范围。窗口范围可以是行数、行的起始和结束位置,或者是行的偏移量。常见的窗口范围包括ROWS UNBOUNDED PRECEDING(从开头到当前行)、ROWS BETWEEN AND (从指定的起始行到结束行)等。

在窗口函数的语法中,窗口范围(Window Frame)用于指定窗口函数计算的行的范围。下面介绍一些常见的窗口范围:

  • ROWS UNBOUNDED PRECEDING:表示从开头到当前行的所有行。
  • ROWS n PRECEDING:表示从当前行向前数n行。
  • ROWS CURRENT ROW:表示只包括当前行。
  • ROWS BETWEEN AND :表示从指定的起始行到结束行的范围,可以使用行号、行的偏移量或特殊关键字。
  • RANGE UNBOUNDED PRECEDING:表示从开头到当前行的所有行,根据排序列的值来确定。
  • RANGE n PRECEDING:表示从当前行向前数n行,根据排序列的值来确定。
  • RANGE CURRENT ROW:表示只包括当前行,根据排序列的值来确定。
  • RANGE BETWEEN AND :表示从指定的起始行到结束行的范围,根据排序列的值来确定。
  • GROUPS UNBOUNDED PRECEDING:表示从开头到当前行的所有组。
  • GROUPS n PRECEDING:表示从当前行向前数n个组。
  • GROUPS CURRENT ROW:表示只包括当前组。
  • GROUPS BETWEEN AND :表示从指定的起始组到结束组的范围。

注意,窗口范围的具体语法和可用选项取决于使用的数据库系统。不同的数据库系统可能会提供不同的窗口范围选项。

使用窗口范围可以更加灵活地控制窗口函数的计算范围,从而满足不同的需求,比如计算移动平均、累计和、百分比等。具体的选择和使用方式,应该根据具体的业务需求和数据库系统的规范来决定。

适用场景

窗口函数在许多场景下非常有用,特别是在需要对查询结果进行分组、排序和计算的情况下。下面是一些窗口函数适用的场景示例:

  • 排名和排序:可以使用窗口函数计算行的排名(ROW_NUMBER)或根据特定列排序(ORDER BY),从而方便地获取排序结果。
  • 分组计算:窗口函数可以在每个分组内进行计算,而无需使用GROUP BY子句。例如,可以计算每个分组的总和、平均值、最大值、最小值等。
  • 移动聚合:窗口函数可以计算滑动窗口范围内的聚合值。例如,可以计算滑动窗口内的移动平均值、移动总和等。
  • 累计计算:窗口函数可以计算累积计算,即将当前行和之前的行进行累积计算。例如,可以计算累积总和、累积平均值等。
  • 分位数和百分比:窗口函数可以计算行在整个结果集中的百分位数,从而方便地获取百分位数和百分比。
  • 前后比较:窗口函数可以比较当前行与前一行或后一行的值,从而进行比较和计算差异。

窗口函数可以在各种情况下提供更灵活和高效的查询和分析功能。它们可以减少复杂的子查询和连接操作,同时提供更直观和简洁的查询语句。无论是在数据分析、报表生成、排名计算还是其他需要对结果集进行计算和处理的场景下,窗口函数都是非常有用的工具。

使用窗口函数解决问题

根据窗口函数语法,我们得到如下查询方案:

WITH RankedRecords AS (
  SELECT id,ref_id,ref_name,created,
    ROW_NUMBER() OVER (PARTITION BY ref_id ORDER BY created DESC) AS rn
  FROM A
)
SELECT id,ref_id,ref_name,created
FROM RankedRecords
WHERE rn = 1;

如此我们便可以查询出我们想要的结果,接下来我们来分析一下:

这个查询使用了窗口函数(Window Function)来实现按照ref_id分组,并根据ref_time进行排序和编号。窗口函数可以在查询结果集中的每一行上计算值,而不会修改结果集本身。

解释步骤:

  • RankRecords子查询:首先,通过在原始表中使用窗口函数ROW_NUMBER(),对每个ref_id进行分组并按照ref_time进行降序排序。这会为每个ref_id的记录分配一个行号(rn),行号从1开始,表示每个ref_id的记录在排序后的顺序。
  • 最外层的SELECT语句:在RankRecords子查询的基础上,选择对应行号(rn)为1的记录,即每个ref_id的最新记录。这样就得到了按照ref_id去重后,每个ref_id的最新记录。

性能比较:

  • 与使用子查询和IN子句的方案相比,使用窗口函数的方案可能会更有效率。原因是窗口函数只需要对表进行一次扫描,并在内部进行排序和编号,而不需要使用子查询和IN子句进行多次查询和匹配。

  • 使用窗口函数的方案可以更简洁地实现按照ref_id去重并获取最新记录的需求,而且在某些情况下可能会更快。但是,实际性能还取决于表的大小、索引的使用情况以及数据库引擎的优化能力。建议在实际环境中进行测试和性能评估,以确定最适合的方案。


问题-分组查询最新一条
https://blog.cikaros.top/doc/67cad2a0.html
作者
Cikaros
发布于
2023年11月14日
许可协议