SQL实现分组查询字段值首次为0的最早记录行
需求说明
现有存储马匹价值随时间衰减数据的记录表,马匹价值随时间推移逐步降低。需实现查询逻辑:提取每匹马价值首次变为0对应的记录行——每匹马均存在多条价值为0的记录,仅需返回其中时间最早的一条,返回结果需包含rowID及全部字段。
本次测试数据中符合要求的是rowID为8和20的两行。
测试表结构与样例数据
| rowID | horseID | valueYear | valueMonth | value | 需提取的目标行 |
|---|---|---|---|---|---|
| 1 | 1 | 1990 | 7 | 1000 | |
| 2 | 1 | 1991 | 1 | 900 | |
| 3 | 1 | 1992 | 2 | 800 | |
| 4 | 1 | 1993 | 4 | 700 | |
| 5 | 1 | 1993 | 7 | 690 | |
| 6 | 1 | 1995 | 3 | 500 | |
| 7 | 1 | 1995 | 7 | 470 | |
| 8 | 1 | 1997 | 8 | 0 | <---- |
| 9 | 1 | 1998 | 2 | 0 | |
| 10 | 1 | 1999 | 3 | 0 | |
| 11 | 1 | 2000 | 9 | 0 | |
| 12 | 2 | 1990 | 3 | 900 | |
| 13 | 2 | 1991 | 1 | 750 | |
| 14 | 2 | 1992 | 7 | 700 | |
| 15 | 2 | 1993 | 3 | 600 | |
| 16 | 2 | 1993 | 4 | 590 | |
| 17 | 2 | 1995 | 3 | 300 | |
| 18 | 2 | 1995 | 9 | 170 | |
| 19 | 2 | 1997 | 5 | 50 | |
| 20 | 2 | 1998 | 6 | 0 | <---- |
| 21 | 2 | 1999 | 4 | 0 | |
| 22 | 2 | 2000 | 2 | 0 |
SQL实现方案
之前使用窗口函数未得到正确结果,通常是两个原因:一是没有先过滤value=0的记录就直接排序,导致非0记录占用排名;二是排序规则错误,没有按年份、月份的先后顺序升序排列。以下提供两种可直接运行的实现:
方案1:窗口函数实现(推荐,适配MySQL 8.0+、PostgreSQL、SQL Server等主流支持窗口函数的引擎)
先筛选所有价值为0的记录,再按马匹分组,按年份升序、月份升序排序,取每组排名为1的记录即可:
SELECT rowID, horseID, valueYear, valueMonth, value FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY horseID ORDER BY valueYear ASC, valueMonth ASC ) AS rn FROM 你的实际表名 -- 替换为真实表名即可 WHERE value = 0 ) t WHERE rn = 1;
注意:不要随意用RANK()替换ROW_NUMBER(),如果同一匹马存在同年月多条value=0的记录,RANK()会返回多条结果,ROW_NUMBER()可保证每组仅取最早的一条,若业务允许同年月多条同取可替换为RANK()。
方案2:关联聚合查询实现(兼容不支持窗口函数的老版本SQL环境)
先通过聚合查询找到每匹马价值为0的最早年月,再关联原表匹配对应记录:
SELECT t1.* FROM 你的实际表名 t1 -- 替换为真实表名即可 INNER JOIN ( SELECT horseID, MIN(valueYear * 100 + valueMonth) AS earliest_ym FROM 你的实际表名 WHERE value = 0 GROUP BY horseID ) t2 ON t1.horseID = t2.horseID AND t1.valueYear * 100 + t1.valueMonth = t2.earliest_ym AND t1.value = 0;
两种方案执行后均会返回rowID=8和rowID=20的两条目标记录,符合需求。
内容的提问来源于stack exchange,提问作者Derek Jee
相关产品推荐
相关产品推荐

