当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL EXCEPT 怎样排除另一查询的结果行

MySQL EXCEPT 怎样排除另一查询的结果行

来源:17golang原创 2026-10-26 20:17:26 0浏览 收藏

要从查询 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 列决定,租户范围分别约束两侧输入。

我倾向于在这里逐列写出字段,避免使用 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;

两侧都限定租户,右侧还明确“已交付”的状态。把所有历史状态都纳入右侧,会把未成功交付的编号也排除。若导出对象其实是“客户 + 活动”组合,两侧就都应选出这两个字段,并在最终排序中使用相同的结果字段。

DISTINCT唯一名单与ALL次数差的结果契约结构图
静态结构说明图:DISTINCT 服务于唯一名单,ALL 服务于重复次数差,排序和审计属于结果契约。

最终 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,不应把不带括号的长表达式按自然阅读顺序猜测。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go HTTP Server 为什么读取请求体后响应变慢Go HTTP Server 为什么读取请求体后响应变慢
上一篇
Go HTTP Server 为什么读取请求体后响应变慢
MySQL INTERSECT 怎样找出两组查询的共同记录
下一篇
MySQL INTERSECT 怎样找出两组查询的共同记录
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    718次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    724次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    801次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    761次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    594次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码