场景说明
假设我们有一张商品表product,存储了不同分类下的商品信息,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 商品ID |
| category | varchar | 商品分类 |
| product_name | varchar | 商品名称 |
| create_time | datetime | 商品上架时间 |

现在的需求是查询每个商品分类下,上架时间最早的第一条商品数据,也就是每个分类的第一条上架商品。
RANK窗口函数基础用法
RANK是SQL中的窗口函数,作用是对分组内的数据按指定排序规则进行排名,相同值的行会获得相同的排名,下一个排名会跳过重复的位数。基本语法如下:
RANK() OVER ( PARTITION BY 分组字段 ORDER BY 排序字段 [ASC|DESC] ) AS 排名别名
其中PARTITION BY用来指定分组的字段,相当于把数据按该字段分成多个组;ORDER BY用来指定每个组内的排序规则,ASC是升序,DESC是降序。
实现每个类别第一条数据查询
结合我们的商品表场景,按category分组,按create_time升序排序,这样每个分类下上架时间最早的商品排名就是1,我们只要过滤排名为1的数据即可。
第一步:给每个分类的商品标记排名
先写子查询或者公用表表达式,为每个分类的商品计算RANK排名:
SELECT
id,
category,
product_name,
create_time,
RANK() OVER (
PARTITION BY category
ORDER BY create_time ASC
) AS rn
FROM product
第二步:过滤排名为1的数据
将上面的查询结果作为临时表,筛选rn等于1的记录,就是每个分类的第一条上架商品:
SELECT
id,
category,
product_name,
create_time
FROM (
SELECT
id,
category,
product_name,
create_time,
RANK() OVER (
PARTITION BY category
ORDER BY create_time ASC
) AS rn
FROM product
) t
WHERE t.rn = 1
注意事项
- 如果同一个分类下有多个商品的上架时间完全相同,RANK会给这些商品都标记为1,此时查询结果会返回多条该分类的第一条数据,如果需要仅返回一条,可以改用
ROW_NUMBER()函数,它会给相同排序值的行分配不同的连续排名。 - 窗口函数不支持直接在WHERE子句中使用,因此需要先通过子查询或者CTE计算出排名,再在外层过滤。
- 排序规则需要根据实际需求调整,如果要取最新的第一条数据,把
ORDER BY create_time ASC改成ORDER BY create_time DESC即可。
替代方案对比
除了RANK函数,也可以使用子查询关联的方式实现,比如先查询每个分类的最小上架时间,再关联原表获取对应数据:
SELECT
p.id,
p.category,
p.product_name,
p.create_time
FROM product p
INNER JOIN (
SELECT
category,
MIN(create_time) AS min_create_time
FROM product
GROUP BY category
) t
ON p.category = t.category
AND p.create_time = t.min_create_time
这种方式在没有窗口函数支持的老版本数据库中可以使用,但逻辑比RANK函数的方式更复杂,且同样存在相同时间返回多条数据的问题,优先推荐使用RANK窗口函数实现。