DISTINCT作用于SELECT列表所有列的组合,而非单列;若需按指定字段去重并保留完整行,须用窗口函数(如ROW_NUMBER())或GROUP BY配合聚合函数。

直接在 DBeaver 里无法“自动去重只留一条”,它不是数据清洗工具,没有内置的 deduplicate 按钮。你需要用 SQL 显式表达去重逻辑,否则 DBeaver 只会忠实地执行你写的语句或展示原始结果。
为什么 SELECT DISTINCT 不够用
SELECT DISTINCT 只能去重「整行完全相同」的记录,但实际中重复往往只发生在部分字段(比如 email 或 phone),其余字段(如 created_at、id)不同。这时 DISTINCT 无能为力,必须靠窗口函数或子查询控制“留哪一条”。
- 常见误判:看到结果里有重复
email,就以为DISTINCT email能解决问题——但它只返回邮箱列,丢掉其他字段,不是“留一条完整记录” - 真正需求是:按
email分组,取其中id最小(或created_at最新)的那条完整行 - 不同数据库语法差异大:PostgreSQL/MySQL 8.0+ 支持
ROW_NUMBER(),SQL Server 同样可用,但旧版 MySQL 需用自连接或变量模拟
PostgreSQL / MySQL 8.0+ / SQL Server 去重推荐写法
用窗口函数最直观可靠,且能明确指定保留策略:
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id ASC) AS rn
FROM users
)
SELECT * FROM ranked WHERE rn = 1;-
PARTITION BY email:按邮箱分组 -
ORDER BY id ASC:每组内按id升序,最小 ID 排第一 →rn = 1就是你要留的那条 - 若想留最新记录,把
ORDER BY id ASC换成ORDER BY created_at DESC - 注意:这个只是查出结果,不修改原表;要删多余行,得用
DELETE配合子查询(见下一条)
如何安全删除重复行(只保留一条)
别直接 DELETE FROM ...,先备份或用事务包裹。以 PostgreSQL 为例:
BEGIN; DELETE FROM users WHERE id NOT IN ( SELECT MIN(id) FROM users GROUP BY email ); -- 检查结果 SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1; -- 确认无误再提交 COMMIT;
-
MIN(id)是关键:确保每组重复中只留下 ID 最小的那条 - SQL Server 需改用
NOT IN (SELECT TOP 1 id ... ORDER BY id)或 CTE +ROW_NUMBER,语法更啰嗦 - MySQL 5.7 及更早版本不支持子查询直接引用目标表,得套一层
SELECT * FROM (SELECT ...) AS tmp - 执行前务必确认
email字段允许 NULL:NULL 会被分到同一组,可能意外合并多条 NULL 邮箱记录
导出后用外部工具处理更稳妥
如果表很大、逻辑复杂,或你不确定 SQL 是否覆盖所有边界情况(比如含 NULL、大小写敏感、空格等),不如导出 CSV 再处理:
- 在 DBeaver 中右键表 →
导出数据→ 选 CSV,勾选包含列标题 - 用 Python pandas:
df.drop_duplicates(subset=['email'], keep='first'),keep可设'first'/'last'/False - 用命令行
sort -t, -k2,2 -u input.csv > dedup.csv(按第2列去重,留第一次出现) - 再通过 DBeaver
导入数据回库——整个过程可验证、可回滚、不锁表
真正容易被忽略的是字段值的隐性差异:大小写、首尾空格、全角/半角字符、NULL 和空字符串混用。这些不会被 GROUP BY 或 ROW_NUMBER 自动归一化,必须在去重前用 TRIM(UPPER(email)) 这类表达式预处理。


















