NOT IN子查询中出现NULL值对结果的影响你注意到了吗

SQL避坑:当in,not in遇上null这种坑你避过吗? 用in的时候一般问题不大,使用not in的时候需要特别主要,结合本文恰当使用,之所以 造成如此大的差异原因是对NULL的判断上,NULL的判断本质上is null或is not null,in或 not in的判断是等于(=),所以对NULL判断是无效的,需要谨记。......... 阅读详情

前言

开发人员写的SQL语句中经常会用到in,exists,not in,not exists 这类子查询,通常,含in、exists的子查询称为半连接(semijoin),含not in、 not exists的子查询被称之为反连接,经常会有技术人员来评论in 与exists 效率孰高孰低的问题,我在SQL优化工作中也经常对这类子查询做优化改写,比如半连接改为内连接,反连接改为外连接等,哪个效率高是要根据执行计划做出判断的,本文不是为了讨论效率问题,是要提醒一点:not in子查询的结果集含NULL值时,会导致整个语句结果集返回空,这可能造成与SQL语句书写初衷不符。

实验

创建实验表t1,t2

greatsql> create table t1(c1 int primary key,c2 varchar(10), key idx_c1(c2));
greatsql> create table t2(c1 int primary key,c2 varchar(10)),key idx_c1(c2));

greatsql> insert into t1 values(1,'a'),(2,'b');
greatsql> insert into t2 values(1,'a'),(2,'c');

观察下面两条语句:

select * from t1 where t1.c2 not in (select t2.c2 from t2);

select * from t1 where not exists (select 1 from t2 where t2.c2=t1.c2);

这两个语句,从表达的含义来看是等价的,都是查询t1表中c2列值在t2表的c2列值中不存在的记录。

从子查询类型来看,第一条语句属于非关联查询,第二条语句属于关联子查询。所谓非关联子查询就是子查询中内查询可以独立执行,与外查询没有关系,互不影响。而关联子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次。

从连接类型来看,使用not in与not exists子查询构造的语句都属于反连接,为了控制连接顺序与连接方式,这种反连接经常被改写为外连接,t1 与t2使用左外连接,条件加上右表t2的连接列 is null,也就是左外连接时没有关联上右表的数据,表达了这个含义“t1表中c2列值在t2表的c2列值中不存在的记录”。反连接改写为外连接,不会导致关联结果集放大,因为没有关联上的t1表数据只显示1条,半连接改为内连接时要注意去重。外连接语句如下所示:

greatsql> select t1.* from t1 left join t2 on t1.c2=t2.c2 where t2.c2 is null;

所以本质表达含义上,上面的三条语句都等价。

下面看一下三条语句的执行结果:

greatsql> select * from t1 where t1.c2 not in (select t2.c2 from t2);
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.00 sec)

greatsql> select * from t1 where not exists (select 1 from t2 where t2.c2=t1.c2);
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.01 sec)

greatsql> select t1.* from t1 left join t2 on t1.c2=t2.c2 where t2.c2 is null;
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.00 sec)

可以看出就目前的数据,三条语句执行结果是相同的。

下面向子查询的t2中插入一条c2列为null的记录。

greatsql> insert into t2 values(3,null);

再观察一下三条语句的执行结果:

greatsql> select * from t1 where t1.c2 not in (select t2.c2 from t2);
Empty set (0.00 sec)

greatsql> select * from t1 where not exists (select 1 from t2 where t2.c2=t1.c2);
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.00 sec)

greatsql> select t1.* from t1 left join t2 on t1.c2=t2.c2 where t2.c2 is null;
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.00 sec)

可以看出,not exists表示的关联子查询与 外连接方式表达的两条语句结果相同,而not in表示的非关联子查询的结果集为空。这是因为子查询select t2.c2 from t2 查询结果含有NULL值导致的。NULL属于未知值,无法与其他值进行比较,无从判断,返回最终结果集为空。这一点在MySQL与Oracle中返回结果都是一致的。如果想表达最初的含义,需要将子查询中NULL值去除。

greatsql> select * from t1 where t1.c2 not in (select t2.c2 from t2 where t2.c2 is not null);
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.02 sec)

那么如果t1表的c2列也插入一条NULL值的记录后,结果集会怎样呢,两个表都存在c2列为NULL的值数据,那么t1表这条NULL值数据能否出现在最终结果集中呢?

greatsql> insert into t1 values(3,null);
Query OK, 1 row affected (0.07 sec)

greatsql> select * from t1 where t1.c2 not in (select t2.c2 from t2 where t2.c2 is not null);
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.00 sec)

greatsql> select * from t1 where not exists (select 1 from t2 where t2.c2=t1.c2);
+----+------+
| c1 | c2   |
+----+------+
|  3 | NULL |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)

greatsql> select t1.* from t1 left join t2 on t1.c2=t2.c2 where t2.c2 is null;
+----+------+
| c1 | c2   |
+----+------+
|  3 | NULL |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)

从执行结果来看,使用not in非关联子查询,其执行结果与其他两条语句的执行结果还是不同,因为t1.c2 使用not in在参与比较时就隐含了t1.c2 is not null的含义,所以最终结果集中不含(3,NULL)这条数据。

而not exists关联子查询,在将外查询的NULL值传递给内查询时执行子查询 select * from t2 where t2.c2=NULL,子查询中找不到记录,所以条件返回false, 表示not exists 为true,则最终结果集中含(3,NULL)这条记录。

左外left join 与 not exists相同,左表的NULL值在右表中关联不上数据,所以要返回(3,NULL)这条数据。这里要注意NULL 不等于 NULL。

greatsql> select NULL=NULL;
+-----------+
| NULL=NULL |
+-----------+
|      NULL |
+-----------+
1 row in set (0.01 sec)

说到这里,GreatSQL支持<=>安全等于这个符号,用来判断NULL值:当两个操作数均为NULL时,其返回值为1而不为NULL;而当一个操作数为NULL时,其返回值为0而不为NULL。

greatsql> select NULL<=>NULL;
+-------------+
| NULL<=>NULL |
+-------------+
|           1 |
+-------------+
1 row in set (0.00 sec)

greatsql> select 1<=>NULL;
+----------+
| 1<=>NULL |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

所以not exists 子查询中的= 换成 <=> 时,最终结果集中去除了(3,NULL)这条数据。

greatsql> select * from t1 where not exists (select 1 from t2 where t2.c2<=>t1.c2);
+----+------+
| c1 | c2   |
+----+------+
|  2 | b    |
+----+------+
1 row in set (0.00 sec)

注意,一般表关联时不使用<=>安全等于这个符号,想象一下,如果关联的两个表在关联字段上都存在很多NULL记录,关联后的结果集对NULL记录的关联是以笛卡尔积的形式体现的,严重影响效率,严格来说关联字段都为NULL值不能算作能匹配上。

结论

  1. 使用not in 的非关联子查询注意NULL值对结果集的影响,为避免出现空结果集,需要子查询中查询列加 is not null条件将NULL值去除。

  2. 实际使用时注意:需求表达的含义是否要将外查询关联字段值为NULL的数据输出,not in隐含了不输出。

  3. 一般认为not exists关联子查询与外连接语句是等价的,可以进行相互改写。

select * from t1 where not exists (select 1 from t2 where t2.c2=t1.c2);

select t1.* from t1 left join t2 on t1.c2=t2.c2 where t2.c2 is null;

如果不需要输出外查询中关联字段为NULL值的数据,还需再加条件 t1.c2 is not null。

select * from t1 where not exists (select 1 from t2 where t2.c2=t1.c2) and t1.c2 is not null;

select t1.* from t1 left join t2 on t1.c2=t2.c2 where t2.c2 is null and t1.c2 is not null;

这样写就与select * from t1 where t1.c2 not in (select t2.c2 from t2 where t2.c2 is not null)等价了。


Enjoy GreatSQL :)

关于 GreatSQL

GreatSQL是适用于金融级应用的国内自主开源数据库,具备高性能、高可靠、高易用性、高安全等多个核心特性,可以作为MySQL或Percona Server的可选替换,用于线上生产环境,且完全免费并兼容MySQL或Percona Server。

相关链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

image

社区有奖建议反馈: https://greatsql.cn/thread-54-1-1.html

社区博客有奖征稿详情: https://greatsql.cn/thread-100-1-1.html

(对文章有疑问或者有独到见解都可以去社区官网提出或分享哦~)

技术交流群:

微信&QQ群:

QQ群:533341697

微信群:添加GreatSQL社区助手(微信号:wanlidbc )好友,待社区助手拉您进群。

在 SAP HANA 里写好反连接:为什么 NOT IN 往往更慢,以及如何用 NOT EXISTS 写得更快更稳 文章摘要: 在SAP HANA中,NOT IN虽然直观但存在性能和语义双重风险。相比NOT EXISTS,NOT IN需要先计算子查询结果再做排除判断,可能导致中间结果物化,增加CPU和内存开销。更严重的是,当子查询包含NULL值时,NOT IN会因SQL三值逻辑返回错误结果。SAP官方建议优先使用NOT EXISTS,并强调声明NOT NULL约束不仅能确保数据质量,还能让优化器进行更高效的查询重写。在业务场景如"查找未下单会员"时,使用NOT EXISTS能同时保证性能和结果准确性。 阅读详情

相关推荐

千兆以太网的接收和crc校验

解析该以太网帧,包括统计该以太网帧长度+过滤功能(端口号对不对,是否为udp协议)+CRC校验,将解析结果存入状态fifo,一旦状态fifo开始有数,就开始开启读过程,读过程开始时,首先读状态fifo,根据从状态fifo里面读出的以太网帧长度,来读数据fifo(这样能够防止跨帧读),然后在根据状态fifo里面读出的crc校验正确标志和过滤标志,来确定是否将数据fifo读出来的数据送给下一个模块。改掉之后在抓一下,可以看到抓出来的数据完全正确,从最后开始看,除去4个crc校验的尾巴,数据是到255结束的。

weixin_45284871的博客 2739

not in遇上null的坑

not in 相当于and条件,只要有一个false,那么所有的都为false,所以查出来的数据固定为空。用于检查子查询是否至少会返回一行数据,强调的是是否返回结果集,不要求知道返回什么。以子查询表B的结果集为驱动,在表A中依次遍历查询id是否在子查询的结果集中存在。以外表表A为驱动表,若括号内的子查询有任意数据返回,表示当前行匹配成功。:not in中的数据过滤掉空值 或 使用。当not in中包含null。

notfound4043的博客 1117

torchsparse安装

torchsparse安装以及安装失败原因分析和解决

AI_BING_的博客 1万+

SQL中的in、not in语句遇到null时的坑点

背景介绍 前两天做问题排查的时候,写了一条sql,但是并没有如期地查到数据(确实是有数据的),SQL如下: SELECT tar.* FROM tb_account_relation tar WHERE tar.customer_id NOT IN ( SELECT customer_id FROM TB_SMS_SEND_DETAIL WHERE TYPE = '2' ) ...

shenzhou_yh的博客 2万+

关于ADS重新安装失败的解决方案

ads卸载安装license被锁无法修改导致安装最后没有证书

weixin_47103290的博客 9702

黑猫带你学UFS协议第17篇:UFS安全模式详解(Secure Mode)

本文详细介绍了UFS协议中的安全模式,包括Erase, Discard, Purge和Wipe Device操作。Erase用于永久删除数据并确保无法恢复;Discard不保证数据清除;Purge类似于Sanitize,清除内部未映射的物理块数据;Wipe Device则是格式化设备。这些操作涉及逻辑单元、擦除单位、相关寄存器配置等,确保数据安全。" 126036669,12409270,MySQL 8.0 开窗函数详解,"['数据库理论', 'SQL']

黑猫奥利奥的博客 2559

【MySQL】使用 IN 或 NOT IN,数据中有 NULL 出现的查询异常情况

MySQL 使用 IN 或 NOT IN,数据中有 NULL 出现的查询异常情况

weixin_50223520的博客 5570

数字电视中的AD(Audio Description)音频描述技术详解

AD(Audio Description)音频描述是一种辅助技术,旨在帮助视障人士更好地理解视觉内容。通过在节目或视频的自然停顿处插入额外的语音叙述来解释屏幕上的视觉信息,如人物动作、场景变化、文字说明等,从而让视力受损的人士也能获得完整的观影体验。AD服务不仅限于电视广播,也广泛应用于电影、剧院表演、在线视频平台等领域。PID:用于标识和区分不同的音频流。PMT表:提供每个音频流的详细信息,包括PID、编解码器类型和语言代码。时间戳:PTS和DTS用于同步音频与视频内容。解复用。

以实战经验介绍行业知识 741

SQL 中in 和not in 遇到NULL 值的查询情况

SQL not in 和 in 遇到NULL值的查询结果

gly1653810310的博客 5863

C++软件调试与异常排查从入门到精通专栏介绍与文章汇总

根据近几年排查软件异常的实践与经验,系统地讲解了C++软件异常常见原因与常用排查方法,以图文并茂的方式给出具体的分析实例,带领大家逐步掌握C++软件异常排查的相关技术与要领。

dvlinker的技术专栏 23万+

解决微信小程序长页面滚动到底部点击底部的输入框后输入框漂浮且页面滚动到顶部

一、问题描述 项目中有一个长页面上部展示部分信息下部为信息录入,页面长度为三屏幕(不同手机屏幕大小略有差异),当滚动到第二屏或者第三屏去填写信息时点击输入框后输入框漂浮起来了并且页面向上滚动了一段距离,回到了第一屏中展示的内容。效果如图。 滚动到第二屏,证件号输入。 点击输入框 可以看到证件号输入框漂浮了起来并且页面滚动到了第一屏展示的内容。 注意:小程序是2018年上线,在2020年4月份之前该页面从未出现过这种现象。推测:微信的后续更新中对微信小程序基础库进行了重构,导致点击输入框后页

仰望星空的代码 3260

Oracle Study之---Oracle IN和NOT IN的使用

Oracle Study之---Oracle IN和NOT IN的使用NOT IN 与 IN 的区别:------------------------------------------------------------------------------------------------------not In 相当于<> all,如果 Not In 后...

weixin_34248487的博客 133

《网络安全自学教程》- Nmap使用及扫描原理分析

使用Nmap进行端口扫描、主机发现、服务识别、操作系统识别、指定脚本、结果导出文件,使用wireshark抓包分析扫描原理。

wangyuxiang946的博客 1万+

exists的用法,跟in有什么区别

这里可以理解这个code表里面SEQUENCE 存了从1自增的自然数 然后DISPLAY_VALUE_CN就是对应的大写 当时CDM_CODEDICT2 里面有1个数据的大写和SEQUENCE 并不匹配 但是我测试的时候没有看到 后面关联的A.DISPLAY_VALUE_CN = B.DISPLAY_VALUE_CN 导致我一直测试前面的 都显示有数据 开始怀疑 exists的用法了和好奇select ‘x’ 这个了 然后开始查阅资料。in 适用于子查询结果集小的场景(需先将子查询结果加载到内存中比对)

weixin_44112620的博客 313

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

mysql sql文的一些坑(2) ---- 子查询的一些问题

今天遇到了 一个问题 其实查询SQL 挺简单的 需求 :查询 名字 存在men表中,但不存在person表 -- (两张表都有这个字段) 思路①: 左连接判断右表为null SELECT * FROM men m LEFT JOIN person p ON m.name = p.name WHERE p.name IS NULL; 思路②: where 条件 刷选出不存在的...

Crayon的博客 742

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

关联查询优化&子查询优化&排序优化

关联查询的优化(都可以自己去实验一下) 数据准备 #分类 CREATE TABLE IF NOT EXISTS `type` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `card` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`) ); #图书 CREATE TABLE IF NOT EXISTS `book` ( `bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMEN

main_Scanner01的博客 1224

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【MySQL学习】分组数据,使用子查询

【MySQL学习】分组数据,使用子查询分组数据数据分组创建分组过滤分组分组和排序SELECT字句顺序使用子查询使用子查询进行过滤作为计算字段使用 分组数据 介绍两个新的SELECT 语句字句,分别是GROUP BY 和 HAVING。 数据分组 之前我们介绍过使用聚集函数汇总数据,比如对1003供应商进行汇总 SELECT COUNT(*) AS num_prods FROM products WHERE vend_id =1003; 说明这个供应商提供7个产品 +-----------+ | num_p

weixin_43561218的博客 584

提高Oracle性能2

<br />索引是表的一个概念部分,用来提高检索数据的效率,Oracle使用了一个复杂的自平衡B-tree结构。通常,通过索引查询数据比全表扫描要快。当 Oracle找出执行查询和Update语句的最好路径时, Oracle优化器将使用索引。同样在联结多个表时使用索引也能够提高效率。另一个使用索引的好处是,他提供了主键(primary key)的唯一性验证。那些LONG或LONG RAW数据类型, 您能够索引几乎任何的列。通常, 在大型表中使用索引特别有效. 当然,您也会发现, 在扫描小表时,使用索引同样能

LX10752p的专栏 498

mysql group by个数_mysql中想知道group by 之后的结果数量,必须用子查询吗?请举个例子...

mysql> select k, count(*) c from tbl group by k order by k limit 5;+---+---+| k | c |+---+---+| 2 | 3 || 4 | 1 || 5 | 2 || 8 | 1 || 9 | 1 |+---+---+5 rows in set (0.00 sec)mysql> explain select ...

weixin_29628635的博客 491

MySQL 分页优化:让查询速度飞起来!

优化方式大分页,带 WHERE大分页,不带 WHERE大分页平均提升比例小分页,带 WHERE小分页,不带 WHERE总体平均提升比例子查询优化28.20%10.60%19.40%24.90%554.40%154.53%INNER JOIN 优化30.80%30.20%30.50%156.50%11.70%57.30%覆盖索引优化(示例)显著提升(具体根据数据和查询而定)显著提升(具体根据数据和查询而定)显著提升(具体根据数据和查询而定)

novelly的专栏 2446

mysql right 效率_mysql left( right ) join使用on 与where 筛选的差异

有这样的一个问题mysql查询使用mysql中left(right)join筛选条件在on与where查询出的数据是否有差异。可能只看着两个关键字看不出任何的问题。那我们使用实际的例子来说到底有没有差异。例如存在两张表结构表结构1drop table if EXISTS A;CREATE TABLE A (ID int(1) NOT NULL,PRIMARY KEY (ID)) ENGINE=M...

weixin_35775778的博客 630

ORACLE优化SQL语句,提高效率(2)

http://space.itpub.net/?uid-219982-action-viewspace-itemid-620027 索引是表的一个概念部分,用来提高检索数据的效率,Oracle使用了一个复杂的自平衡B-tree结构。通常,通过索引查询数据比全表扫描要快。当Oracle找出执行查询和Update语句的最好路径时, Oracle优化器将使用索引。同样在联结多个表时使用索引也能够提高效...

nlslzf的专栏 138

mysql update语句分区更新_MySQL的SQL语句 - 数据操作语句(15)- UPDATE 语句

UPDATE 语句UPDATE 是修改表中行的 DML 语句。UPDATE 语句可以用 WITH 子句开头,定义在 UPDATE 中可访问的公共表表达式。单表语法:1. UPDATE [LOW_PRIORITY] [IGNORE] table_reference2. SET assignment_list3. [WHERE where_condition]4. [ORDER...

weixin_34355360的博客 1862

MySQL(五):数据约束与表设计

约束是关系型数据库系统强制执行的一种数据验证规则。它通常在创建表或修改表结构时,定义在特定的列(字段)或列组合上从底层实现来看,约束是数据库管理系统在执行数据变更操作(如 INSERT、UPDATE、DELETE)时自动触发的校验机制。任何不符合约束规则的数据操作请求,都会被数据库拒绝执行并抛出错误。通过这种方式,约束将业务逻辑中的数据规范直接沉淀到了数据库层面默认值是绑定在表字段上的一个属性。

cui_ruicheng的博客 1236

mysql right join on_解析mysql left( right ) join使用on与where筛选的差异

有这样的一个问题mysql查询使用mysql中left(right)join筛选条件在on与where查询出的数据是否有差异。可能只看着两个关键字看不出任何的问题。那我们使用实际的例子来说到底有没有差异。例如存在两张表结构表结构1drop table if EXISTS A;CREATE TABLE A (ID int(1) NOT NULL,PRIMARY KEY (ID)) ENGINE=M...

weixin_33099377的博客 218
上一篇: 编译GreatSQL with RocksDB引擎
下一篇: 故障解析丨一次死锁问题的解决
GreatSQL社区
博客等级 码龄5年 1210粉丝 390原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值