在 MySQL 中查询重复数据是日常数据清洗和校验的常见需求,核心思路是通过 分组(GROUP BY
) 统计字段重复次数,再用 过滤条件(HAVING
) 筛选出重复记录。以下分场景详细介绍查询方法,覆盖 “单字段重复”“多字段组合重复”“查询完整重复行” 等核心需求,并提供去重思路。
重复数据的判定依赖 “重复维度”(单字段 / 多字段),关键语法组合:
-
GROUP BY 重复字段
:按目标字段分组,相同值的记录会被归为一组;
-
COUNT(*) >= 2
:统计每组的记录数,大于等于 2 表示存在重复;
-
HAVING
:过滤分组后的结果(区别于 WHERE
过滤行数据,HAVING
仅作用于分组)。
需求:查询某一列中存在重复的值(如 “用户表中重复的手机号”“订单表中重复的订单号”)。
适用于快速定位 “哪些值重复了”“重复了多少次”。
语法:
SELECT
重复字段名,
COUNT(*) AS 重复次数
FROM
表名
GROUP BY
重复字段名
HAVING
COUNT(*) >= 2;
示例:查询 user
表中重复的 phone
(手机号)及重复次数:
SELECT
phone,
COUNT(*) AS 重复次数
FROM
user
GROUP BY
phone
HAVING
COUNT(*) >= 2;
结果示例:
适用于需要查看 “重复记录的全部信息”(如重复手机号对应的用户 ID、姓名),需用 子查询 + 关联 实现(先定位重复字段值,再关联原表查完整行)。
语法:
SELECT
*
FROM
表名
WHERE
重复字段名 IN (
SELECT 重复字段名
FROM 表名
GROUP BY 重复字段名
HAVING COUNT(*) >= 2
)
ORDER BY
重复字段名;
示例:查询 user
表中所有手机号重复的完整用户记录:
SELECT
*
FROM
user
WHERE
phone IN (
SELECT phone
FROM user
GROUP BY phone
HAVING COUNT(*) >= 2
)
ORDER BY
phone;
结果示例:
需求:判定 “多个字段同时相同” 为重复(如 “订单表中同一用户在同一时间创建的订单”“学生表中同一班级、同一姓名的学生”)。
核心:GROUP BY
后接 多个字段,仅当所有字段值均相同时才会被归为一组。
语法:
SELECT
字段1,
字段2,
COUNT(*) AS 重复次数
FROM
表名
GROUP BY
字段1, 字段2
HAVING
COUNT(*) >= 2;
示例:查询 order
表中 “同一用户(user_id)在同一时间(create_time)创建的重复订单”:
SELECT
user_id,
create_time,
COUNT(*) AS 重复次数
FROM
`order`
GROUP BY
user_id, create_time
HAVING
COUNT(*) >= 2;
语法:
SELECT
*
FROM
表名
WHERE
(字段1, 字段2) IN (
SELECT 字段1, 字段2
FROM 表名
GROUP BY 字段1, 字段2
HAVING COUNT(*) >= 2
)
ORDER BY
字段1, 字段2;
示例:查询 order
表中多字段重复的完整订单记录:
SELECT
*
FROM
`order`
WHERE
(user_id, create_time) IN (
SELECT user_id, create_time
FROM `order`
GROUP BY user_id, create_time
HAVING COUNT(*) >= 2
)
ORDER BY
user_id, create_time;
需求:表中存在主键(唯一标识,如 id
),但其他字段完全相同(即 “仅主键不同,其余字段均相同” 的重复行)。
语法:
SELECT
*
FROM
表名 t1
WHERE
EXISTS (
SELECT 1
FROM 表名 t2
WHERE
t1.主键字段 != t2.主键字段
AND t1.字段1 = t2.字段1
AND t1.字段2 = t2.字段2
)
ORDER BY
字段1, 字段2;
示例:查询 user
表中 “仅 id 不同,其余字段均相同” 的重复记录:
SELECT
*
FROM
user t1
WHERE
EXISTS (
SELECT 1
FROM user t2
WHERE
t1.id != t2.id
AND t1.name = t2.name
AND t1.phone = t2.phone
AND t1.create_time = t2.create_time
)
ORDER BY
name, phone;
查询到重复数据后,通常需要保留一条、删除其余重复行,核心是通过 主键 / 唯一标识 区分保留行和删除行:
DELETE FROM
user
WHERE
phone IN (
SELECT phone FROM (
SELECT phone
FROM user
GROUP BY phone
HAVING COUNT(*) >= 2
) AS temp
)
AND id NOT IN (
SELECT MIN(id) FROM (
SELECT MIN(id) AS id
FROM user
GROUP BY phone
HAVING COUNT(*) >= 2
) AS temp2
);
说明:MySQL 不允许直接在 DELETE
的 WHERE
中引用当前删除的表,因此需要用 子查询嵌套(加别名) 规避该限制。
-
性能问题:查询大表时,
GROUP BY
可能消耗较多资源,建议:
-
对
GROUP BY
的字段创建索引(如单字段重复时建 phone
索引,多字段重复时建 (user_id, create_time)
联合索引);
-
避免用
SELECT *
,仅查询需要的字段。
-
NULL 值处理:
GROUP BY
中,NULL
会被视为相同值(即多个 NULL
会被归为一组);若需排除 NULL
,可在 WHERE
中加 字段名 IS NOT NULL
。
-
关键字表名 / 字段名:若表名(如
order
)或字段名(如 desc
)是 MySQL 关键字,需用 反引号(`) 包裹,避免语法错误。