news 2026/10/1 2:06:46

SQL面试常见问题:查询及删除重复记录的方法与TaoToken实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL面试常见问题:查询及删除重复记录的方法与TaoToken实践

1. 面试官为什么总爱问“重复记录怎么删”

SQL 面试里有一类题几乎逢考必出:给你一张用户表,里面有几条姓名和手机号完全一样的记录,让你先查出来,再删掉多余的,只保留一条。看起来简单,但真正写起来,很多人会卡在“保留哪一条”“怎么保证不误删”“删完怎么验证”这几个点上。

这个问题的核心检索词就是SQL 查询重复记录和SQL 删除重复记录。它考的不是你会不会写DELETE,而是你对GROUP BY、HAVING、窗口函数、子查询这几块的理解是否扎实。面试官想看到的是:你能不能先定位重复,再安全地删除,最后用行数对比证明结果正确。

我试过在真实项目里处理一张 200 万行的订单表,重复数据来自上游同步任务重跑,当时用ROW_NUMBER()配合 CTE 一次性清理干净,整个过程不到 3 秒。所以这类题不是纸上谈兵,它是实打实的生产技能。

先明确一个概念:重复记录分两种。第一种是完全重复,所有字段的值都一模一样;第二种是关键字段重复,比如name相同但age不同,业务上认为这算重复,需要保留一条。两种情况的处理方式不一样,面试时最好主动区分,这样能加分。

下面我用一张具体的表来演示。假设表名是users,结构如下:

CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), phone VARCHAR(20), city VARCHAR(50) ); INSERT INTO users VALUES (1, '张三', '13800001111', '北京'), (2, '张三', '13800001111', '北京'), (3, '李四', '13900002222', '上海'), (4, '李四', '13900002222', '上海'), (5, '王五', '13700003333', '广州'), (6, '张三', '13800001111', '北京');

这张表里,id=1,2,6是完全重复的,id=3,4也是完全重复的。接下来所有操作都基于这张表展开。

2. 用 GROUP BY + HAVING 定位重复记录

2.1 查询完全重复的记录

第一步永远是“先查再删”。直接删是危险的,你得先看清楚有多少重复、重复在哪。用GROUP BY加HAVING是最直观的方式:

SELECT name, phone, city, COUNT(*) AS cnt FROM users GROUP BY name, phone, city HAVING COUNT(*) > 1;

执行结果:

namephonecitycnt
张三13800001111北京3
李四13900002222上海2

这里GROUP BY把所有字段都列上,HAVING COUNT(*) > 1筛出出现次数大于 1 的组合。注意WHERE不能替代HAVING,因为聚合函数的结果必须在分组之后才能过滤,这是面试常问的细节。

2.2 查询关键字段重复的记录

如果业务只关心name重复,不管其他字段,那就只对name分组:

SELECT name, COUNT(*) AS cnt FROM users GROUP BY name HAVING COUNT(*) > 1;

结果会显示张三 3 条、李四 2 条。这时候你拿到的是“哪些 name 重复了”,但还没拿到具体是哪几行。要拿到完整行,需要把子查询套回去:

SELECT * FROM users WHERE name IN ( SELECT name FROM users GROUP BY name HAVING COUNT(*) > 1 ) ORDER BY name, id;

这样就能看到所有重复 name 对应的完整记录,方便你决定保留哪一条。面试时如果只写前半段,面试官可能会追问“那具体是哪几行重复”,所以这一步最好主动补上。

2.3 用窗口函数标记重复行

GROUP BY适合“查有多少”,但如果你想知道“每一行是不是重复的”,窗口函数更合适:

SELECT *, ROW_NUMBER() OVER (PARTITION BY name, phone, city ORDER BY id) AS rn FROM users;

结果里rn=1的是每组的第一条,rn>1的就是多余记录。这个rn字段是后面删除操作的关键。PARTITION BY后面跟的就是你判断重复的依据字段,ORDER BY id决定保留哪一条——这里保留 id 最小的。

3. 可复制的去重配置与删除语句

3.1 删除完全重复、保留最小 id

最稳妥的写法是用 CTE 加ROW_NUMBER(),先标记再删除:

WITH ranked AS ( SELECT id, ROW_NUMBER() OVER (PARTITION BY name, phone, city ORDER BY id) AS rn FROM users ) DELETE FROM users WHERE id IN (SELECT id FROM ranked WHERE rn > 1);

这条语句的逻辑是:按name, phone, city分组,组内按id升序编号,编号大于 1 的就是要删的。执行后id=2,4,6会被删除,剩下id=1,3,5。

如果你用的是 MySQL 5.7 以下版本,不支持 CTE,可以改成自连接:

DELETE u1 FROM users u1 JOIN users u2 ON u1.name = u2.name AND u1.phone = u2.phone AND u1.city = u2.city AND u1.id > u2.id;

这条语句的意思是:只要存在一条 id 更小的相同记录,当前这条就删掉。效果和上面一致。

3.2 删除关键字段重复、保留最小 id

如果只按name去重,把PARTITION BY改成name即可:

WITH ranked AS ( SELECT id, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS rn FROM users ) DELETE FROM users WHERE id IN (SELECT id FROM ranked WHERE rn > 1);

执行后每个 name 只保留 id 最小的那条,张三保留 id=1,李四保留 id=3,王五保留 id=5。

3.3 在 TaoToken 通道下让模型帮你生成和校验 SQL

写 SQL 的时候,尤其是窗口函数嵌套删除这种,很容易漏掉边界条件。我习惯用 TaoToken 的统一 API 通道调模型来帮我生成初稿,再自己审一遍。配置很简单,三件套是 Base URL、API Key、Model ID。

如果你用的是 Cline 这类支持 MCP 的编辑器插件,配置片段如下:

{ "mcpServers": { "taotoken": { "url": "https://taotoken.net/api", "headers": { "Authorization": "Bearer 你的API_KEY" }, "model": "claude-sonnet-4-20250514" } } }

如果你用的是 Claude Code,可以在项目根目录的.claude/settings.json里写:

{ "apiBaseUrl": "https://taotoken.net/api", "apiKey": "你的API_KEY", "model": "claude-sonnet-4-20250514" }

Codex 用户则在~/.codex/auth.json里配置:

{ "base_url": "https://taotoken.net/api", "api_key": "你的API_KEY", "model": "claude-sonnet-4-20250514" }

配好之后,你可以直接把表结构和需求丢给模型:“这张表按 name 去重,保留 id 最小的,给我一条 PostgreSQL 的 DELETE 语句,并解释为什么不能用 GROUP BY 直接删。”模型会给你一条带 CTE 的语句,还会提醒你DELETE不能直接跟聚合函数。拿到之后你在测试库跑一遍,确认行数变化符合预期,再上生产。

API Key 在控制台的 API Keys 页面生成,模型对话入口可以先用模型对话页面快速验证通道是否通。

4. 验证请求与执行前后行数对比

4.1 执行前先记录行数

删除之前一定要先查总数,这是验证的基础:

SELECT COUNT(*) AS total_before FROM users;

假设结果是 6。删除之后再查一次:

SELECT COUNT(*) AS total_after FROM users;

如果结果是 3,说明删掉了 3 条重复记录,符合预期。这个前后对比是面试时展示你严谨性的好机会,很多人只写删除语句,忘了验证。

4.2 用查询确认剩余记录

光看行数还不够,还要确认剩下的是不是每组最小 id:

SELECT * FROM users ORDER BY id;

预期结果:

idnamephonecity
1张三13800001111北京
3李四13900002222上海
5王五13700003333广州

如果结果和这个一致,说明去重成功。如果发现某个 name 一条都没了,那说明PARTITION BY或ORDER BY写错了,需要回滚重来。

4.3 用模型辅助校验 SQL 逻辑

有时候你自己写的 SQL 跑出来结果对,但逻辑上可能有隐患。比如ROW_NUMBER()的ORDER BY如果用了非唯一字段,可能导致每次执行保留的行不一样。这时候可以让模型帮你审一遍:

请检查这条 SQL 是否存在不确定性:ROW_NUMBER() OVER (PARTITION BY name ORDER BY id),如果 id 有重复会怎样?

模型会告诉你,如果id有重复,ROW_NUMBER()的结果可能不稳定,建议加一个唯一字段做 tie-breaker。这种校验在面试里也能体现你的深度。

5. 本篇常见报错与排查

5.1 报错:You can't specify target table for update in FROM clause

这是 MySQL 的经典报错。你写:

DELETE FROM users WHERE id NOT IN ( SELECT MIN(id) FROM users GROUP BY name );

MySQL 不允许在DELETE的子查询里直接引用被删的表。解决办法是套一层派生表:

DELETE FROM users WHERE id NOT IN ( SELECT id FROM ( SELECT MIN(id) AS id FROM users GROUP BY name ) AS tmp );

或者直接用 CTE 加ROW_NUMBER(),绕开这个问题。

5.2 报错:local proxy failed 或 401

如果你在 TaoToken 通道下调模型生成 SQL 时报local proxy failed,先检查 Base URL 是不是写成了https://taotoken.net/api,有没有多写斜杠或漏掉/api。报401则是 API Key 无效或过期,去控制台的 API Keys 页面重新生成一个,替换配置里的 Key。注意 Key 不要提交到 Git,用环境变量或本地配置文件。

5.3 报错:reading choices 时解析失败

这个通常出现在流式响应里,模型返回的 JSON 结构和你客户端预期的不一致。检查你的 Model ID 是否写对,比如claude-sonnet-4-20250514不要写成claude-sonnet-4。如果用的是 Cline,确认 MCP 配置里的model字段和实际调用的模型一致。改完配置后重启编辑器插件再试。

5.4 删除后行数没变

如果DELETE执行了但行数没变,先看是不是没有COMMIT。有些客户端默认手动提交,你需要显式执行COMMIT;。另外检查WHERE条件是不是写成了rn = 1,那样删的是要保留的行,结果会反过来。执行前先用SELECT把要删的 id 列出来,确认无误再改成DELETE。

5.5 OAuth 相关报错

如果你在 Claude Code 里配置了 TaoToken 但仍然走 OAuth 流程报错,检查settings.json里是否同时存在旧的 OAuth 配置。把冲突的字段删掉,只保留apiBaseUrl、apiKey、model三项。保存后重启 Claude Code,让它重新读取配置。

6. 把去重能力接到你的日常开发流里

面试题只是入口,真正有价值的是把“查重—去重—验证”这套流程固化下来。我的做法是:在 TaoToken 的 Coding Plan 里建一个常用提示词模板,每次遇到重复数据问题,直接调模型生成 SQL 初稿,然后自己在测试库跑一遍行数对比。这样既快又不容易出错。

如果你经常处理数据清洗,建议把ROW_NUMBER()这套写法记牢,它比GROUP BY加自连接更直观,也更容易扩展到“保留最新一条”“保留金额最大一条”这类变体。需要长期做编码和 Agent 任务的话,Coding Plan 的额度比按次调用更划算。

最后留一个实用技巧:删除前先CREATE TABLE users_backup AS SELECT * FROM users;,万一删错了还能恢复。这个习惯在面试里说出来,面试官会觉得你有生产意识。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 2:06:03

个人开发者单卡RTX 3090实战:GPT-2预训练与领域适配全流程

1. 为什么个人开发者也要走一遍LLM全流程很多人一提到大模型,第一反应就是“这玩意儿得几百张卡才能玩”。我一开始也这么想,直到自己用一张RTX 3090把GPT-2从预训练一路做到领域适配,才发现个人开发者和工业级团队之间的差距,其实…

作者头像 李华
网站建设 2026/10/1 2:05:46

OpenDaylight安装避坑指南:Java环境、版本兼容与Karaf启动全解析

1. 这不是普通软件安装:OpenDaylight 是网络操作系统,装错一步就卡在 Karaf 控制台里出不来OpenDaylight(ODL)不是你点几下“下一步”就能装好的桌面应用。它本质是一个基于 OSGi 架构的、面向 SDN(软件定义网络&#…

作者头像 李华
网站建设 2026/10/1 2:05:42

go-redis 实战:在 Redis Cluster 中使用 MGET 批量高效获取多键值

后端数据库客户端缓存 【免费下载链接】go-redis Redis Go client 项目地址: https://gitcode.com/GitHub_Trending/go/go-redis 点击查看 免费下载 本文基于 go-redis 仓库中的 cluster-mget 示例 及其 main.go,系统讲解如何用 redis.NewClusterClient…

作者头像 李华