MySQL EXCEPT 怎样排除另一查询的结果行
来源:17golang原创
时间:2026-10-26 20:17:26 478浏览 收藏
要从查询 A 中排除查询 B 已有的结果行,可以把两条查询写成 A EXCEPT B。比较单位是两侧选出的完整行,默认去重;需要保留重复次数差时显式使用 EXCEPT ALL。左右交换会改变含义,EXCEPT 也不会删除原表数据。
本文以 MySQL 8.4 为说明环境;该语法从 MySQL 8.0.31 开始支持,不能只看“8.0”就判断可用。官方入口:https://dev.mysql.com/doc/refman/8.4/en/except.html。下面用“同一租户的待导出编号减去已交付编号”说明写法,示例为原创推演,未在本文环境执行。
先保护查询范围:差集不等于权限过滤
在设计导出名单时,我会先把要保护的对象写下来:候选编号、所属租户,以及排除依据。这样看 SQL 时,注意力不会只停留在 EXCEPT 这个关键字上。它只负责差集,不能自动替应用补上访问范围。
例如某租户的候选编号中有 102,如果右侧查询把其他租户的 102 也拿来比较,当前租户的候选就可能被错误排除。这个问题首先影响结果完整性;如果左侧本身漏了范围约束,差集也不会阻止越权数据进入结果。租户值应来自已授权的服务端上下文,并通过驱动参数绑定传入。
这里的风险分级按后果理解:漏掉左侧范围可能涉及数据暴露,漏掉右侧范围可能造成误排除,重复语义选错则会改变导出数量。本文只围绕这条差集查询处理这些边界。
沿着列投影找出误排除的路径
EXCEPT 不会根据列名推测主键,也不会自动找关联键。左右列数必须一致,对应位置应使用一致的数据类型和业务含义。如果左侧选出 customer_id, status,右侧也选出这两列,那么编号相同而状态不同的行仍是不同元组。只想按编号排除,就让两侧都只投影编号。

我倾向于在这里逐列写出字段,避免使用 SELECT *:表结构增加字段后,参与比较的内容可能随之变化。最终列名来自第一个查询块;对应位置类型不同时 MySQL 会综合两侧决定结果类型,因此也不要依靠隐式转换修正业务口径。字符串比较还需要关注列的字符集与排序规则。
用独立样例明确 DISTINCT 与 ALL
先用只读 CTE 构造一组可理解的数据。左侧 102 出现三次,右侧出现一次;左侧还有 101、103,右侧另有 104。下面每一条 SQL 都是独立语句,不创建或修改业务表。
-- 唯一名单:右侧出现过的编号从左侧集合中排除
WITH candidate AS (
SELECT 101 AS customer_id UNION ALL
SELECT 102 UNION ALL SELECT 102 UNION ALL SELECT 102 UNION ALL
SELECT 103
), delivered AS (
SELECT 102 AS customer_id UNION ALL SELECT 104
)
SELECT customer_id FROM candidate
EXCEPT DISTINCT
SELECT customer_id FROM delivered
ORDER BY customer_id;
按照集合语义,预期只剩 101、103。即使左侧有多条 102,DISTINCT 也不会留下其中两条;右侧出现过该值,就足以从唯一名单中排除它。省略 DISTINCT 是同一个含义。
-- 次数差:同值在左侧的次数减去右侧次数,最少为零
WITH candidate AS (
SELECT 101 AS customer_id UNION ALL
SELECT 102 UNION ALL SELECT 102 UNION ALL SELECT 102 UNION ALL
SELECT 103
), delivered AS (
SELECT 102 AS customer_id UNION ALL SELECT 104
)
SELECT customer_id FROM candidate
EXCEPT ALL
SELECT customer_id FROM delivered
ORDER BY customer_id;
这条的预期序列为 101、102、102、103:102 剩余两次。一般可把某个元组的剩余次数理解为 max(左侧次数 - 右侧次数, 0)。ALL 适合要保留多重集合次数的任务,不等于“右侧有这个编号也保留左侧全部重复行”。如果记录各有独立业务身份,应把真实身份列纳入契约,而不是指望重复次数替代身份。
把租户范围与比较列写进查询
回到导出场景,假定两个查询来源都包含 tenant_id、customer_id,交付来源还有 delivery_status。下面只生成唯一客户编号,不携带姓名等额外信息。问号是数据库驱动的预处理参数,占位值在应用中绑定,不能原样当作已填值 SQL 粘贴到普通客户端运行。
-- 两个参数分别绑定同一授权租户,不能由任意请求字段替代授权判断 SELECT customer_id FROM export_candidate WHERE tenant_id = ? EXCEPT DISTINCT SELECT customer_id FROM delivery_record WHERE tenant_id = ? AND delivery_status = 'delivered' ORDER BY customer_id;
两侧都限定租户,右侧还明确“已交付”的状态。把所有历史状态都纳入右侧,会把未成功交付的编号也排除。若导出对象其实是“客户 + 活动”组合,两侧就都应选出这两个字段,并在最终排序中使用相同的结果字段。

最终 ORDER BY 控制差集结果的显示顺序;不能依赖原表顺序。如果要对某一侧单独做 ORDER BY、LIMIT,需要用括号明确查询块范围。先给左侧加 LIMIT 再做差集,表达的是“先截取候选再排除”,并不保证最终还能得到同样数量的行。
给结果留下可追溯的口径
我会把这条查询的口径与导出任务一起记录:授权租户、候选范围、右侧状态条件、比较列、DISTINCT 或 ALL、执行时间以及结果数量。这里的“审计”是保留业务解释,不是把原始名单和敏感字段无差别写进日志。
如果事后需要重现同一结果,仅存 SQL 文本还不够:源数据可能已经变化。应在任务设计中说明结果文件或快照的保存策略,避免把两次不同时间的查询误认为同一数据状态。也不要宣称 EXCEPT 必然更快;数据量、重复分布及实际执行计划都会影响成本。
语法与版本依据分别见 MySQL EXCEPT 官方说明、集合查询的列、排序与组合规则和 8.0.31 发布说明。本文的租户导出场景与样例数据为原创设计。
使用前逐项确认结果含义
- 服务器版本支持 EXCEPT,不能把所有 MySQL 8.0 版本视为相同。
- 左侧是要保留的候选,右侧是排除依据,两侧范围已分别明确。
- 比较列数量、位置、类型与业务含义一致,额外说明字段没有混入比较键。
- 唯一名单选择 DISTINCT;确实需要次数相减才使用 ALL。
- 最终结果有明确排序,LIMIT 的位置与业务要求相符。
为什么同一个编号还是留在结果里?
先看两侧是否还选了其他列。EXCEPT 比较完整元组,相同编号搭配不同状态,并不是相同行。然后再看右侧条件是否实际覆盖了目标编号。
EXCEPT 会删除候选表中的行吗?
不会。本文的语句返回查询结果,原始行仍在来源表中。不要把得到差集名单误当成完成了删除或状态更新。
反过来写能得到同样结果吗?
不能保证。A EXCEPT B 与 B EXCEPT A 表达不同方向,右侧独有的 104 就是本例中的直观区别。
多个集合运算混用时怎样表达清楚?
用括号写清业务分组。MySQL 的 INTERSECT 优先于 UNION 和 EXCEPT,不应把不带括号的长表达式按自然阅读顺序猜测。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
272 收藏
-
163 收藏
-
482 收藏
-
164 收藏
-
341 收藏
-
391 收藏
-
260 收藏
-
323 收藏
-
318 收藏
-
159 收藏
-
181 收藏
-
386 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习