前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >必知必会——关于SQL中的NOT IN优化

必知必会——关于SQL中的NOT IN优化

作者头像
MySQLSE
发布2020-09-28 15:21:06
4.4K0
发布2020-09-28 15:21:06
举报

作者:Guilhem Bichot 译:徐轶韬

如果您使用 SELECT…WHERE x NOT IN(SELECT y FROM…)等“ NOT IN”编写SQL查询,必须了解当“ x”或“ y”为NULL时会发生什么?如果不是您想要的结果,我将在这里告诉您如何解决。

首先,一个简单的情况:如果“ x”和“ y”是使用NOT NULL子句创建的列,则它们永远不会为NULL。让我们考虑其他情况。复杂性源于以下事实:NULL可以理解为“未指定,可能是任何东西”,因此SQL的观点是,它无法知道NULL是否等于一个值或“东西”。产生的答案既不是TRUE,也不是FALSE,它产生UNKNOWN,MySQL将其打印为NULL:

在开始之前,我们需要记住另外两个SQL细节:

  • WHERE针对行测试条件,并且仅当此条件为TRUE时才让行通过(拒绝FALSE和UNKNOWN)。
  • NOT(TRUE)为FALSE,NOT(FALSE)为TRUE,NOT(UNKNOWN)为UNKNOWN。

现在我们已经准备好,让我们看一下这个例子:

这是一张房屋清单表,我们都知道用于房屋加热的能源类型包括(“煤”,“木材”,“天然气”等,或者不加热时为NULL)。

查看所有用煤炭或木材加热的房屋:

查看其他房屋:

我们有一间不带暖气的房子A,另一间使用油的房子B:

当我们测试房屋A时:

加热 IN (“coal”, “wood”) -> UNKNOWN,用SQL表示加热为NULL时,NULL可能是coal,也可能是wood,或者不是,我们不知道…

加热 NOT IN (“coal”, “wood”) -> UNKNOWN 因为NOT IN应用于NOT且IN是UNKNOWN,所以NOT(UNKNOWN)是UNKNOWN。

作为结果:

因为WHERE消除了条件不为TRUE的行,所以消除了房屋A。从SQL的角度来看,上面两个SELECT的结果是正确的。现在轮到您决定它们是否符合您的期望。

如果符合预期,那么一切都很好。但是我知道对于某些人来说,这不是他们所期望的。例如,有些人震惊地看到IN和NOT IN都错过了A房屋,就像A不在两组(“coal”, “wood”和另一组)中一样;似乎是看不见的,有点像幽灵……

问题的关键是当我设计房屋表时,我的意思是NULL为“无”,“无暖气”。

与SQL的理解不同,SQL意味着NULL为“也许是煤炭,天然气或其他,或者什么都不是”。 因此,就我的意图而言,NULL不可能是煤炭或木材,因此我希望IN不返回A,并且我希望NOT IN返回A。

那么,应该怎么做才能使NOT IN表现出预期的效果?

简单!我只需要用SQL更好地表达我想要的内容即可。 我可以将NOT IN更改为IN NOT TRUE:

这将让IN返回FALSE或UNKNOWN的房屋通过;因此,A和B会如我所愿地通过。

“ NOT IN(子查询)”也会发生相同的问题。让我们添加此表:

查询加热不产生二氧化碳的房屋:

>没有结果。

再次缺少A。同样,解决方案是:

现在我得到A。将其重写为IN IS NOT TRUE效果很好。

我可以改写为NOT EXISTS,但这是需要更多的编辑工作:

这也返回A。

如果我做两次重写中的任何一个,我就会以某种方式向MySQL声明我希望NULL是我的NOT IN的明确匹配项。另一个好处是,这还使MySQL可以更“积极地”进行优化。当NOT IN的任何一侧是可为空的列(此处是我们的情况)时,

SELECT … WHERE heating NOT IN (SELECT name …)

不能转换为反联接(MySQL 8.0.17的新功能),因为NOT IN与NULL的行为与关系代数中反联接的定义不匹配。因此,MySQL在执行此查询的方式上受到限制。

但,SELECT … WHERE heating IN (SELECT name …) IS NOT TRUE

可以转换为反连接。对于NOT EXISTS重写也是如此。

我们可以在EXPLAIN中进行检查;首先,我们有一个初始的NOT IN,其中一个查询计划显示每个房屋执行一个子查询,并且每次都进行表扫描(这效率很低):

现在,这是重写的查询,它们正确地使用了反联接,因此可以从我们新的基于哈希的联接算法中受益(在版本8.0.18中引入了内联接,并在8.0.20中扩展为半联接,反联接和外部联接):

反连接计划确实更快。为了进行实验检验,我们创建一百万个随机房屋:

RAND()返回0到1之间的数字;ROUND()*5将其舍入为0到5之间的整数;0到4获得真实的能源,而5获得NULL(因为在CASE中未指定5)。

要获得一百万个房屋,我只需要重复上一次的INSERT几次。现在我的搜索查询时间是:

反联接计划以更少的百分之二十的时间返回更多的行(如预期的那样,包括NULL)。

得出的结论是:当使用NOT IN时,如果无法避免使用NULL,请确认NULL的行为,如果不符合预期,请考虑“不正确或不存在”的替代方案。

感谢您使用MySQL!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MySQL解决方案工程师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档