SQL 如何按 NAME 去重并优先保留 NEW 状态?
结论
题目中的两列数据可以用 GROUP BY NAME 和条件聚合,得到每个名称对应的优先状态,不必让主表与自身做 inner join。先筛选出 NEW 和 CURRENT,再检查每组是否有 NEW:有则返回 NEW,否则返回 CURRENT。
SELECT
NAME,
CASE
WHEN MAX(CASE WHEN STATUS = 'NEW' THEN 1 ELSE 0 END) = 1
THEN 'NEW'
ELSE 'CURRENT'
END AS STATUS
FROM MyTable
WHERE STATUS IN ('NEW', 'CURRENT')
GROUP BY NAME;
过滤后,每组至少有一条 NEW 或 CURRENT 记录。因此,没有 NEW 的组只会包含 CURRENT,ELSE 'CURRENT' 就适用。只有 MODIFIED 等其他状态的名称会被 WHERE 排除。
按示例数据,结果如下:
| NAME | STATUS |
|---|---|
| 1 | NEW |
| 2 | NEW |
| 3 | CURRENT |
题中两种写法的取舍
把状态映射为数字后取最小值,可以表示 NEW 优先于 CURRENT。不过,原查询重复计算了同一个 MIN(CASE ...),还把 DISTINCT 和 GROUP BY 一起使用,这两处都没有必要。上面的 MAX 条件聚合可以直接判断组内是否存在 NEW。
用 LISTAGG 拼接状态字符串,再通过 LIKE 搜索,不适合这个判断。它需要先拼接字符串再进行模式匹配,依赖状态的文本表示;如果状态文本包含搜索片段,也可能造成误判。这里无需构造状态列表。
需要返回完整原始行时
如果实际表中还有其他列,并且要从每个 NAME 对应的记录中选出一条完整原始行,可以用 ROW_NUMBER() 排序后筛选第一名:
SELECT NAME, STATUS
FROM (
SELECT
t.*,
ROW_NUMBER() OVER (
PARTITION BY NAME
ORDER BY CASE STATUS
WHEN 'NEW' THEN 1
WHEN 'CURRENT' THEN 2
ELSE 3
END
) AS rn
FROM MyTable t
WHERE STATUS IN ('NEW', 'CURRENT')
)
WHERE rn = 1;
如果同一个 NAME 和 STATUS 下有多条记录,而且需要稳定地选出一条,应在 ORDER BY 中加入业务认可的排序列,例如时间列或唯一键。题目没有提供这类列,因此无法指定具体的平局规则。
大数据量下的考虑
两种写法都要按 NAME 分组或分区,实际耗时取决于数据量、数据分布、索引和执行计划。只有 NAME、STATUS 需要处理时,可以先用条件聚合;需要完整行时,再用分析函数,并根据实际执行计划评估。SQL 写法本身无法说明哪种方案在所有数据规模下都更快。
备注:内容仅供参考。
