首页 / 资讯中心 / 文章详情

LeetCode 196 删除重复的电子邮箱题解:MySQL 三种 DELETE 写法与 Pandas 原地去重

LeetCode 196 删除重复的电子邮箱题解:MySQL 三种 DELETE 写法与 Pandas 原地去重 ★ FEATURED ARTICLE
示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本篇题解围绕 LeetCode 196「删除重复的电子邮箱」展开完整讲解题目对Person表的删除要求并给出三种可运行的 MySQLDELETE方案聚合子查询、窗口函数、自连接多表删除以及一种 Pandas 原地去重方案。读完本文你将掌握「同一分组只保留最小 id 行」这类数据库去重删除问题的通用解题范式并能根据 MySQL 版本与数据规模选择合适的写法。题目理解表结构与删除目标题目给出一张Person表结构如下Column NameTypeidintemailvarchar其中id是该表的主键具有唯一值的列每一行包含一封电子邮件且电子邮件不包含大写字母。删除规则只有一条删除所有重复的电子邮件只保留一个具有最小id的唯一电子邮件。题目还特别强调了两点约束对于 SQL 用户必须编写DELETE语句而不是SELECT语句对于 Pandas 用户必须直接原地修改Person表。运行脚本后展示的是处理完毕的Person表表中最终顺序无关紧要只看哪些行被保留。示例推演输入Person 表: ---------------------- | id | email | ---------------------- | 1 | johnexample.com | | 2 | bobexample.com | | 3 | johnexample.com | ----------------------输出---------------------- | id | email | ---------------------- | 1 | johnexample.com | | 2 | bobexample.com | ----------------------johnexample.com出现了两次id 为 1 和 3保留其中最小的id 1删除 id 为 3 的行bobexample.com本身不重复原样保留。解法一聚合子查询 NOT IN最经典写法核心思路「同一邮箱只留 id 最小的那一行」可以等价转化为按邮箱分组求出每组的最小 id得到一个保留集合然后删除 id 不在这个集合里的所有行。MySQL 实现DELETE FROM Person WHERE id NOT IN (SELECT MIN(id) FROM (SELECT * FROM Person) AS p GROUP BY email);逐层拆解这条语句SELECT * FROM Person AS p先把Person整体复制为一张派生表derived tablepGROUP BY email按邮箱分组MIN(id)取出每个邮箱分组中的最小 id得到保留集合外层DELETE FROM Person WHERE id NOT IN (...)删除所有不在保留集合中的行。为什么必须多包一层子查询这是本题最值得注意的坑MySQL 不允许在FROM子句中直接指定正在被更新/删除的目标表否则会报错You cant specify target table for update in FROM clause错误码 1093。所以必须先把Person包成派生表p再对p做聚合从而绕开对正在删除的表做聚合的限制。另外由于id是主键、非空这里的NOT IN子查询结果中不会出现NULL因此不会踩到NOT IN遇到NULL时整体结果失效的经典陷阱。该解法对应仓库中的 Solution.sql逻辑与题解文档中「方法一」一致。解法二窗口函数 ROW_NUMBER 标记重复行核心思路方法一用聚合隐式表达每个邮箱的最小 id方法二则用窗口函数显式给每一行编号按email分区、按id升序排列编号为 1 的行即该邮箱应保留的行编号大于 1 的都是重复行直接删除。MySQL 实现DELETE FROM Person WHERE id NOT IN ( SELECT id FROM ( SELECT id, ROW_NUMBER() OVER ( PARTITION BY email ORDER BY id ) AS rk FROM Person ) AS p WHERE rk 1 );逐层拆解内层ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rk在每个email分区内按id升序编号最小的id编号为 1外层WHERE rk 1选出所有应保留行的 id最后DELETE FROM Person WHERE id NOT IN (...)删除剩余行。与解法一相比这种写法把每个邮箱保留谁从聚合逻辑变成了编号逻辑语义更直观、更易读。它同样需要派生表包装以满足 MySQL 对目标表不能直接出现在FROM中的限制。需要注意ROW_NUMBER()属于窗口函数需要MySQL 8.0 或更高版本才支持若运行在 5.7 及以下版本应改用解法一或解法三。该解法对应仓库中的 Solution2.sql。解法三自连接 DELETE JOIN 直接删除大 id核心思路前两种解法都需要先算出保留集合再删除其余行。解法三换了一个角度不必预先计算保留集合——把表和自己按email相等自连接凡是存在同一邮箱、且另一行id更小的行就是应该被删除的重复行一条DELETE JOIN直接完成删除。MySQL 实现DELETE p2 FROM person AS p1 JOIN person AS p2 ON p1.email p2.email WHERE p1.id p2.id;执行逻辑解读person AS p1 JOIN person AS p2 ON p1.email p2.email把person与自身连接连接条件是两行邮箱相同WHERE p1.id p2.id对于同一邮箱的两行id较大的那一行p2就是要删除的重复行DELETE p2 FROM ...这是 MySQL 多表删除语法明确只删除p2一侧的行p1保持不动。例如示例数据中johnexample.com有两行id 1 与 id 3自连接产生(p11, p23)与(p13, p21)两个匹配对其中满足p1.id p2.id的只有(1, 3)因此 id 为 3 的行被删除id 为 1 的行保留。整条语句不会误删任何邮箱中 id 最小的那一行因为不存在比它更小的同行邮箱记录。两点补充说明该解法要求数据库支持多表DELETE语法MySQL 原生支持语句中表名写作小写person。MySQL 中表名是否区分大小写取决于lower_case_table_names系统变量实际使用时应与建表时的表名保持一致。该解法对应仓库中的 Solution3.sql实现最为精炼。使用 Pandas 原地修改 Person 表题目允许 Pandas 用户直接修改Person表仓库中的 Solution.py 给出了完整实现import pandas as pd # Modify Person in place def delete_duplicate_emails(person: pd.DataFrame) - None: # Sort the rows based on id (Ascending order) person.sort_values(byid, ascendingTrue, inplaceTrue) # Drop the duplicates based on email. person.drop_duplicates(subsetemail, keepfirst, inplaceTrue)两个关键步骤sort_values(byid, ascendingTrue, inplaceTrue)先按id升序排序。排序的意义在于drop_duplicates的keepfirst语义是保留第一次出现的行只有排序后才能保证每个邮箱第一次出现的行恰好是id最小的那一行drop_duplicates(subsetemail, keepfirst, inplaceTrue)以email列为判重依据保留每个邮箱首次出现的行即id最小者删除其余重复行。两个操作均使用inplaceTrue原地修改函数不返回新表、返回None完全契合题目直接修改Person表的要求。三种 SQL 解法对比与选型维度解法一聚合子查询解法二窗口函数解法三自连接 DELETE JOIN核心思路聚合求出保留集合删除集合外行按邮箱分区编号删除编号大于 1 的行自连接后直接删除 id 较大的一侧是否依赖窗口函数否是需 MySQL 8.0否是否依赖多表 DELETE否否是语句条数/嵌套单条、双层子查询单条、双层子查询单条、无子查询语义直观度中需理解聚合派生表高编号逻辑直白高连接即删除选型建议追求兼容性MySQL 5.7 及以下选解法一或解法三追求可读性且版本支持解法二的ROW_NUMBER编号语义最直观也最容易推广到保留前 N 条的变体场景追求语句最简解法三一条DELETE JOIN完成全部逻辑无需任何子查询。三种写法都已在仓库中提供可直接运行的完整文件Solution.sql、Solution2.sql、Solution3.sql配合 中文题解文档 与 英文题解文档 对照学习效果更佳。仓库中的配套资源与延伸阅读本仓库将数据库类题目集中收录于数据库专项练习清单中0196 题位列其中可按题号快速检索全部数据库题解。与本题互为对照的还有 0182「查找重复的电子邮箱」solution/0100-0199/0182.Duplicate%20Emails/README.md那题要求用SELECT找出重复邮箱本题则要求用DELETE删除重复行。把两题放在一起练习可以完整覆盖重复数据这一主题下的查询与删除两类操作加深对聚合、窗口函数与自连接三种去重手段的理解。赞分享示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐把 PDF「画出来」再排查pdfplumber 可视化调试与 PDF 转图像完整指南把 PDF「画出来」再排查pdfplumber 可视化调试与 PDF 转图像完整指南 提取 PDF 里的文本、表格时结果总是差一点字符错位、表格断行、边界数据分析文档删除有序数组中的重复项LeetCode 26一次遍历原地去重全解删除有序数组中的重复项LeetCode 26一次遍历原地去重全解 本文基于 doocs/leetcode 仓库中 26. 删除有序数组中的重复项 http示例工程删除排序链表中的重复元素LeetCode 83一次遍历的 O(1) 空间原地去重解法删除排序链表中的重复元素LeetCode 83一次遍历的 O 1 空间原地去重解法 本文基于 LeetCode 83 题题解文档 https://link示例工程上一篇3步完成Word到LaTeX的完美转换docx2tex专业指南下一篇diff-pdf终极指南免费开源的PDF差异检测神器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站