SQL实现:多列分组(ID_1/ID_2/ID_3)下选取每日每组首行
解决多列分组下每日每个建筑取第一行数据的SQL方案
嗨,刚接触SQL碰到这种多列联合标识的分组需求确实容易懵,毕竟大部分入门案例都是单列分组~ 下面给你几个通用且易理解的方案,几乎适配所有主流数据库(MySQL、PostgreSQL、SQL Server等):
方案一:用窗口函数ROW_NUMBER()(最推荐)
这是目前最灵活的解决方案,不管你定义“第一行”的规则是按时间最早、还是其他字段排序,都能轻松实现。核心思路是:
- 按日期+三个建筑ID作为分组维度(PARTITION BY)
- 给每个分组内的行按你需要的规则排序(比如按记录生成时间升序,取最早的那条)
- 筛选出每个分组里排序为1的行
示例SQL代码:
SELECT * FROM ( SELECT *, -- 按日期和三个ID分组,每组内按create_time升序编号,最早的行编号为1 ROW_NUMBER() OVER ( PARTITION BY record_date, ID_1, ID_2, ID_3 ORDER BY create_time ASC ) AS row_num FROM building_records ) AS ranked_records WHERE row_num = 1;
说明:
- 把
record_date换成你实际的日期列名,create_time换成用来判断“第一行”的排序字段(比如如果没有单独的时间戳,也可以用其他业务字段排序) - 如果你的“第一行”规则是取最新的记录,把
ORDER BY create_time ASC改成DESC就行
方案二:子查询+关联(适用于有唯一时间戳的场景)
如果你的表有唯一的时间戳字段(比如每条记录的生成时间是唯一的),可以先找出每个建筑每日的最早时间戳,再通过时间戳+建筑ID关联回原表取完整数据:
SELECT br.* FROM building_records br INNER JOIN ( -- 先找到每个日期+建筑ID对应的最早时间戳 SELECT record_date, ID_1, ID_2, ID_3, MIN(create_time) AS earliest_time FROM building_records GROUP BY record_date, ID_1, ID_2, ID_3 ) AS earliest_records ON br.record_date = earliest_records.record_date AND br.ID_1 = earliest_records.ID_1 AND br.ID_2 = earliest_records.ID_2 AND br.ID_3 = earliest_records.ID_3 AND br.create_time = earliest_records.earliest_time;
说明:
- 这个方案的前提是
create_time在每个record_date+ID_1+ID_2+ID_3分组里是唯一的,否则可能会返回多条相同时间的记录 - 相比窗口函数,这个方案的可读性稍差,但在某些旧版本数据库(比如MySQL 5.7及以前不支持窗口函数)里是必要的选择
方案三:用DISTINCT ON(仅PostgreSQL支持)
如果你用的是PostgreSQL,还有个更简洁的写法:
SELECT DISTINCT ON (record_date, ID_1, ID_2, ID_3) * FROM building_records ORDER BY record_date, ID_1, ID_2, ID_3, create_time ASC;
说明:
DISTINCT ON后面跟着的是分组维度,PostgreSQL会返回每个分组里按ORDER BY排序后的第一行数据- 注意
ORDER BY的开头必须包含DISTINCT ON里的所有字段,否则会报错
内容的提问来源于stack exchange,提问作者MattM
相关产品推荐
相关产品推荐

