SQL查询去重时怎么只保留最新更新时间的那一条核心数据?

2026-07-09数据库165003

ROW_NUMBER()按时间降序编号取rn=1是最通用清晰的去重方案,适用于MySQL 8.0+、PostgreSQL等支持窗口函数的数据库;老版本MySQL可用GROUP BY+子查询匹配最大时间;清理数据时推荐自连接删非最新行。

用 ROW_NUMBER() 按时间排序后取 row_num = 1

这是跨数据库最通用、语义最清晰的做法,尤其适合需要“保留最新时间那条”的场景。核心是把每组重复记录按时间字段(比如 updated_atcreated_at)降序编号,再筛出编号为 1 的行。

常见错误是写成 ORDER BY updated_at ASC——这会保留最老的那条,不是你要的“最新”。

  • 必须用 PARTITION BY 明确分组字段(如 user_idemail),不能漏
  • ORDER BY updated_at DESC 才能确保最新时间排第一
  • MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;MySQL 5.7 及更早不支持窗口函数,得换方案
  • 示例(查 users 表中每个 email 最新一条):
    WITH ranked AS (
      SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY updated_at DESC) AS rn
      FROM users
    )
    SELECT id, email, updated_at FROM ranked WHERE rn = 1;

用 GROUP BY + 子查询关联找最大时间点

适用于不支持窗口函数的老版本 MySQL(如 5.7),或你只想查数据、不删数据的轻量场景。本质是先算出每组的 MAX(updated_at),再反向匹配原表中对应时间戳的完整行。

容易踩的坑是:如果同一组里有两条记录时间完全相同(比如秒级精度下并发写入),这个方法可能返回多条——它不保证“只一条”,只保证“时间最新”。

  • 子查询必须包含所有用于判断重复的字段(如 email)和时间字段(MAX(updated_at)
  • 主查询 JOIN 条件要同时匹配分组字段和最大时间值,缺一不可
  • 示例:
    SELECT u1.*
    FROM users u1
    JOIN (
      SELECT email, MAX(updated_at) AS max_time
      FROM users
      GROUP BY email
    ) u2 ON u1.email = u2.email AND u1.updated_at = u2.max_time;
  • 若字段允许 NULL,GROUP BY email 会把所有 NULL 归为一组,需提前用 WHERE email IS NOT NULL 过滤

DELETE 时直接删掉非最新行(MySQL/PostgreSQL)

真要清理脏数据,不能只查,得删。但直接 DELETE ... WHERE id NOT IN (...) 容易锁表或超内存,推荐用自连接方式逐组比对 ID 或时间戳。

关键点在于:比较逻辑必须严格对应“保留最新”的定义。用时间字段比 ID 更可靠——ID 大不一定更新晚,尤其当数据来自不同导入批次或软删除恢复时。

  • MySQL 写法(按 email 分组,删 updated_at 更小的):
    DELETE u1 FROM users u1
    INNER JOIN users u2
    WHERE u1.email = u2.email
      AND u1.updated_at < u2.updated_at;
  • PostgreSQL 要改用 USING 语法,且不能在 USING 子句里引用别名 u1,得写成:
    DELETE FROM users u1
    USING users u2
    WHERE u1.email = u2.email
      AND u1.updated_at < u2.updated_at;
  • 执行前务必加事务并测试:BEGIN; DELETE ...; SELECT COUNT(*) FROM users; 确认删得对再 COMMIT

索引没建好,再好的 SQL 也慢得像卡住

所有上述方案在大表(比如百万级以上)上跑,如果没有针对性索引,要么超时,要么拖垮库。这不是可选项,是必做项。

很多人试了几次都失败,最后发现只是缺一个联合索引——不是字段单独有索引就行,必须是组合的。

  • 判重字段 + 时间字段要建联合索引,顺序很重要:先写分组字段(如 email),再写排序字段(如 updated_at),即 INDEX(email, updated_at)
  • 如果查询还涉及其他 WHERE 条件(比如 status = 'active'),要把这些也加进索引前缀
  • EXPLAIN 看执行计划:确认 typerefrange,没出现 Using filesortUsing temporary
  • 没索引时,GROUP BY email 在千万级表上可能吃光内存,直接 OOM

真正难的不是写出某条 SQL,而是判断“最新”到底由什么定义——是时间戳?是自增 ID?还是业务侧人工标记的 version 字段?一旦定义模糊,所有去重逻辑都会偏移。而且线上删数据前,没人会替你备份。

本文地址:https://www.ufcn.cn/article/2431005.html

如非特殊说明,本站内容均来自于网友自主分享,概不代表本站观点,如有任何问题我们都将在收到反馈后的第一时间进行处理!