sql没有万能“更新重复数据并保留最新记录”语句,必须拆解为识别最新记录+删除旧记录或覆盖写入两步;核心依赖row_number()或max()+子查询,具体选择取决于查询、删除或upsert场景。

直接说结论:SQL 本身没有“更新重复数据并保留最新记录”这一条万能语句,必须拆解为「识别最新记录」+「删除旧记录」或「覆盖写入」两步;核心靠 ROW_NUMBER() 或 MAX() + 子查询,具体选哪种取决于你用的是查询、删除,还是 UPSERT 场景。
用 ROW_NUMBER() 删除旧重复行(最常用)
这是 SQL Server、PostgreSQL、MySQL 8.0+ 的标准做法。关键不是“更新”,而是先标记再删——把非最新行干掉,剩下自然就是最新。
-
PARTITION BY字段必须是你认为“重复”的依据,比如ProductName、customer_id或多个列组合:PARTITION BY order_number, product -
ORDER BY必须是能定义“最新”的字段,优先级:时间戳(create_time DESC) > 自增 ID(id DESC);注意DESC不能漏,否则会留最老的 - CTE 中不能直接
DELETE FROM CTE(除 SQL Server 外多数不支持),要套一层子查询或用IN关联主键 - 示例(删 Sales 表中同
ProductName的旧记录):
WITH dupes AS (
SELECT ID,
ROW_NUMBER() OVER (
PARTITION BY ProductName
ORDER BY OrderDate DESC, ID DESC
) AS rn
FROM Sales
)
DELETE FROM Sales
WHERE ID IN (SELECT ID FROM dupes WHERE rn > 1);
用 MAX() + 自连接查最新记录(兼容老版本 MySQL)
如果你在用 MySQL 5.7 或更早版本,ROW_NUMBER() 不可用,就得靠聚合 + 连接。它不删数据,只查出每组最新的一行,适合只读或导出场景。
- 子查询里
GROUP BY的字段必须和外层JOIN条件完全一致,少一个就可能漏匹配 -
MAX(create_time)只能保证时间最新,但如果同一时间有多个记录,会随机返回一个(没去重逻辑);此时必须加第二排序维度,比如MAX(id),但MAX()无法跨字段取整行,所以这种方案天然有歧义 - 不要用
SELECT *直接跟GROUP BY,MySQL 5.7 严格模式会报错;必须显式列出所有非聚合字段或改用ANY_VALUE() - 示例(查 orders 表中每个
order_id的最新记录):
SELECT o1.* FROM orders o1 INNER JOIN ( SELECT order_id, MAX(order_date) AS max_date FROM orders GROUP BY order_id ) o2 ON o1.order_id = o2.order_id AND o1.order_date = o2.max_date;
用 MERGE 实现“存在则更新,不存在则插入”(UPSERT)
这不是“删旧留新”,而是“按唯一键判断是否已存在”。适用于同步上游变更、ETL 场景。SQL Server 支持完整 MERGE,MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,PostgreSQL 用 INSERT ... ON CONFLICT。
- 前提:目标表必须有明确的唯一约束或主键(如
UNIQUE(product_name)),否则MERGE不知道拿什么去ON -
MERGE的WHEN MATCHED THEN UPDATE是整行覆盖,不会自动忽略 NULL 字段;如果只想更新部分列,得显式写出来 - 并发下可能触发死锁,尤其当多线程同时 Upsert 同一 key 时;建议加
HOLDLOCK(SQL Server)或控制应用层重试 - SQL Server 示例(以
ProductName为唯一标识同步 Sales 表):
MERGE Sales AS target
USING (VALUES ('笔记本X1', '2026-07-20')) AS source (ProductName, OrderDate)
ON target.ProductName = source.ProductName
WHEN MATCHED THEN
UPDATE SET OrderDate = source.OrderDate
WHEN NOT MATCHED THEN
INSERT (ProductName, OrderDate) VALUES (source.ProductName, source.OrderDate);
真正容易被忽略的点是:所谓“最新”必须有明确定义——是最后插入?最后修改?还是业务上最后一次有效状态?时间字段如果有 NULL、时区混用、精度不一致(DATETIME vs DATETIME2(7)),ORDER BY ... DESC 就可能排错。别假设数据干净,先 SELECT 看一眼分组内的时间分布再说。











