首页 > 其他分享 >NOT IN子查询中出现NULL值对结果的影响你注意到了吗

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

时间:2024-03-15 10:33:09浏览次数:22  
标签:查询 +----+------+ t2 t1 注意 c2 NULL select

前言

开发人员写的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

标签:查询,+----+------+,t2,t1,注意,c2,NULL,select
From: https://www.cnblogs.com/greatsql/p/18074890

相关文章

  • python的代码发布到服务器上需要注意的事项
    1、服务器的python运行环境配置。从官网上 https://www.python.org/ 下载服务器操作系统对应的版本。 然后配置 python和pip命令运行的环境变量,这是windows下的 检查是否正常:  2、开发时引用的第三方库要在服务器上安装。   a.首先获取需要的第三方库......
  • 基于微信小程序的公交信息在线查询系统小程序设计与实现(源码+lw+部署文档+讲解等)
    文章目录前言项目运行截图技术框架后端采用SpringBoot框架前端框架Vue可行性分析系统测试系统测试的目的系统功能测试数据库表设计代码参考数据库脚本为什么选择我?获取源码前言......
  • 在VS2022和VS2012共存的电脑上安装VS212扩展注意事项
    1、下载一个专用于VS2012的扩展文件,直接双击下载的扩展文件安装后,显示安装成功。但是你可能会发现在VS2022,VS2012中都找不到此扩展。这就要求必须用VS2022的命令将其卸载,然后再用VS2012进行安装。2、用VS2022的命令卸载打开VS2022开发工具先找出此扩展的vsixid:将原扩展文件......
  • 【PG】查询数据大小
    #!/bin/bash##################################################################calculate_a_pure_size_of_the_postgres_db.sh##ThisscriptcalculatesapuresizeofthePostgresDB##DB_Pure_Size=DB_Size-DB_Tables_Bloat-DB_Indexes_Bloat##Aut......
  • YOLOv9改进策略:注意力机制 |通道注意力和空间注意力CBAM | GAM超越CBAM,不计成本提高精
    ......
  • 注意!运用表单流程管理可一起实现提质增效
    经常会有客户在我们面前抱怨:到底用什么样的方法和软件,才能实现高效率的办公?其实,大家不必苦恼。因为低代码技术平台服务商流辰信息将会给大家推荐表单流程管理的实用性,它的灵活简便、易操作等优势特点,可以帮助大家实现高效率办公,创造更高市场价值。今天,我们一起来了解表单流程管理......
  • MYSQL学习笔记26: 多表查询|子查询
    多表查询|子查询行子查询查询与张无忌工资相同,且直属领导相同的员工#写法1select*fromempwheresalary=(selectsalaryfromempwherename='张无忌')andmanagerId=(selectmanagerIdfromempwherename='张无忌');#可以合并起来,写入一个集合selec......
  • Mysql 100个表嵌套查询 存储过程
    背景1. 业务销售订单会随机落在1~100表中,查询一个订单时需要1到100表依次去查询,增加手工重复操作和浪费时间。2.查询未解冻数据时,需要过滤部分解冻的数据,此时需要用到嵌套查询。 一、根据订单号,循环1~100个表,查询出订单数据CREATEDEFINER=`{数据库连接账号}`@`%`......
  • 利用jmeter命令行模式对influxdb查询api进行压力测试
    需求说明物联网指标数据(车辆的轨迹、里程、速度等)存储在influxDB中。需要对指标查询服务的性能做压力测试。背景1、javaservice已经对influxDB查询sql做了封装,api参数如下:{"deviceIds":["14146284645"],"startTime":1710259200000,"endTime":17104320......
  • 【2024-完整版】python爬虫 批量查询自己所有CSDN文章的质量分:附整个实现流程
    【2024】批量查询CSDN文章质量分写在最前面一、分析获取步骤二、获取文章列表1.前期准备2.获取文章的接口3.接口测试(更新重点)三、查询质量分1.前期准备2.获取文章的接口3.接口测试四、python代码实现1.分步实现2.批量获取文章信息3.从excel中读取文章url,查询......