SQL查询需求:相同phoneNumber仅返首行,NULL行全部保留
解决相同phoneNumber仅返回首行、NULL行全部保留的查询需求
我来帮你搞定这个问题——你遇到的情况确实是DISTINCT的局限性:SQL标准里所有NULL会被视为相同值,所以DISTINCT只能返回一条NULL行,完全不符合你的需求。下面用窗口函数的方案就能完美解决,而且适配绝大多数主流数据库。
核心思路
用ROW_NUMBER()窗口函数做分组编号:
- 对于非NULL的
phoneNumber,按相同值分组,给每组内的行按你指定的规则(比如创建时间、主键顺序)编号,只保留编号为1的行(也就是每组的第一行)。 - 对于NULL的
phoneNumber,给每一行分配一个唯一的分组依据(比如主键、临时UUID),让每一行单独成为一个分组,这样编号自然都是1,所有NULL行都会被保留。
通用示例代码(推荐用主键做唯一标识)
假设你的表名为your_table,主键是id,需要保留的字段包括phoneNumber、username、create_time等:
SELECT id, phoneNumber, username, create_time -- 明确列出需要的字段,避免用* FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY CASE WHEN phoneNumber IS NOT NULL THEN phoneNumber ELSE id -- 用主键作为NULL行的唯一分组依据 END ORDER BY create_time ASC -- 指定排序规则,确定“第一行”的顺序(比如最早创建的行) ) AS row_num FROM your_table ) sub_query WHERE row_num = 1;
不同数据库的适配方案
如果你的表没有主键,或者需要更灵活的实现,可以根据数据库类型调整:
- MySQL:用
UUID()生成临时唯一值
SELECT phoneNumber, username, create_time FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY CASE WHEN phoneNumber IS NOT NULL THEN phoneNumber ELSE UUID() END ORDER BY id ASC ) AS row_num FROM your_table ) t WHERE row_num = 1;
- PostgreSQL:用
ctid(行物理位置标识)代替主键
SELECT phoneNumber, username, create_time FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY CASE WHEN phoneNumber IS NOT NULL THEN phoneNumber ELSE ctid END ORDER BY created_at ASC ) AS row_num FROM your_table ) t WHERE row_num = 1;
- SQL Server:用
NEWID()生成唯一值
SELECT phoneNumber, username, create_time FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY CASE WHEN phoneNumber IS NOT NULL THEN phoneNumber ELSE NEWID() END ORDER BY id ASC ) AS row_num FROM your_table ) t WHERE row_num = 1;
关键注意事项
- 一定要指定
ORDER BY子句:如果不指定,数据库会随机返回分组内的某一行,“第一行”的结果完全不可控。 - 生产环境避免用
SELECT *:明确列出需要的字段,既提升查询效率,也能避免后续表结构变更带来的意外问题。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

