如何从各列选取非NULL的TOP 1值?同ID多行合并为一行
解决同ID多行合并为单行(取非NULL值)的方案
嘿,这个需求我经常碰到,核心就是把同ID的多条记录聚合起来,提取每列的非NULL值——不管是任意非NULL还是按顺序的TOP1,都有对应的解决方案,而且完全不用WHERE子句。下面分几种主流数据库给你具体实现:
基础通用方案(不关心非NULL值顺序)
如果你的需求只是“只要列有非NULL值就展示,全NULL则返回NULL”,不关心取哪一个非NULL值,那么用MAX()或MIN()聚合函数是最简单高效的,因为它们会自动忽略NULL值,对每个ID分组后返回该列的非NULL值(如果有多个,返回最大/最小的,但满足“非NULL就展示”的要求)。
MySQL/MariaDB、PostgreSQL、SQL Server 通用代码
SELECT id, MAX(column1) AS column1, MAX(column2) AS column2, MAX(column3) AS column3 -- 继续添加你需要的所有列 FROM your_table GROUP BY id;
把your_table换成你的表名,column1/column2换成实际要提取的列即可
按指定顺序取TOP1非NULL值
如果需要按特定逻辑(比如创建时间最早、更新时间最晚)取第一个出现的非NULL值,就需要用到窗口函数,不同数据库的语法略有差异:
MySQL 8.0.22+ 版本
MySQL 8.0.22及以上支持IGNORE NULLS关键字,可以直接跳过NULL值取第一个非NULL:
SELECT DISTINCT id, FIRST_VALUE(column1 IGNORE NULLS) OVER (PARTITION BY id ORDER BY create_time ASC) AS column1, FIRST_VALUE(column2 IGNORE NULLS) OVER (PARTITION BY id ORDER BY create_time ASC) AS column2 FROM your_table;
create_time换成你用来排序的字段,ASC是升序(最早的在前),DESC是降序(最晚的在前)
PostgreSQL
PostgreSQL原生支持IGNORE NULLS,语法更简洁:
SELECT DISTINCT id, FIRST_VALUE(column1) OVER (PARTITION BY id ORDER BY create_time ASC IGNORE NULLS) AS column1, FIRST_VALUE(column2) OVER (PARTITION BY id ORDER BY create_time ASC IGNORE NULLS) AS column2 FROM your_table;
SQL Server 2022+ 版本
SQL Server 2022开始支持IGNORE NULLS,用法如下:
SELECT DISTINCT id, FIRST_VALUE(column1) OVER (PARTITION BY id ORDER BY create_time ASC ROWS UNBOUNDED PRECEDING IGNORE NULLS) AS column1, FIRST_VALUE(column2) OVER (PARTITION BY id ORDER BY create_time ASC ROWS UNBOUNDED PRECEDING IGNORE NULLS) AS column2 FROM your_table;
旧版SQL Server(无IGNORE NULLS支持)
如果是2022之前的版本,可以用ROW_NUMBER()来优先排序非NULL行,再提取第一个值:
WITH ranked_data AS ( SELECT id, column1, column2, -- 给column1的非NULL行排前面,再按时间排序 ROW_NUMBER() OVER (PARTITION BY id ORDER BY CASE WHEN column1 IS NOT NULL THEN 0 ELSE 1 END, create_time ASC) AS rn1, -- 同理处理column2 ROW_NUMBER() OVER (PARTITION BY id ORDER BY CASE WHEN column2 IS NOT NULL THEN 0 ELSE 1 END, create_time ASC) AS rn2 FROM your_table ) SELECT id, MAX(CASE WHEN rn1 = 1 THEN column1 END) AS column1, MAX(CASE WHEN rn2 = 1 THEN column2 END) AS column2 FROM ranked_data GROUP BY id;
关键说明
- 所有方案都不需要
WHERE子句,直接处理全表数据,你之前用WHERE只是测试缩小范围,现在去掉即可。 - 如果你的表数据量很大,优先用分组聚合的方案(
MAX()/MIN()),性能比窗口函数更好;如果有顺序要求,再用窗口函数版本。
内容的提问来源于stack exchange,提问作者Jordon Griffith
相关产品推荐
相关产品推荐

