自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

NOT IN子查詢中出現(xiàn)NULL值對結(jié)果的影響你注意到了嗎

數(shù)據(jù)庫 其他數(shù)據(jù)庫
本文不是為了討論效率問題,是要提醒一點(diǎn):not in子查詢的結(jié)果集含NULL值時,會導(dǎo)致整個語句結(jié)果集返回空,這可能造成與SQL語句書寫初衷不符。

前言

開發(fā)人員寫的SQL語句中經(jīng)常會用到in,exists,not in,not exists 這類子查詢,通常,含in、exists的子查詢稱為半連接(semijoin),含not in、 not exists的子查詢被稱之為反連接,經(jīng)常會有技術(shù)人員來評論in 與exists 效率孰高孰低的問題,我在SQL優(yōu)化工作中也經(jīng)常對這類子查詢做優(yōu)化改寫,比如半連接改為內(nèi)連接,反連接改為外連接等,哪個效率高是要根據(jù)執(zhí)行計劃做出判斷的,本文不是為了討論效率問題,是要提醒一點(diǎn):not in子查詢的結(jié)果集含NULL值時,會導(dǎo)致整個語句結(jié)果集返回空,這可能造成與SQL語句書寫初衷不符。

實(shí)驗(yàn)

創(chuàng)建實(shí)驗(yàn)表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);

這兩個語句,從表達(dá)的含義來看是等價的,都是查詢t1表中c2列值在t2表的c2列值中不存在的記錄。

從子查詢類型來看,第一條語句屬于非關(guān)聯(lián)查詢,第二條語句屬于關(guān)聯(lián)子查詢。所謂非關(guān)聯(lián)子查詢就是子查詢中內(nèi)查詢可以獨(dú)立執(zhí)行,與外查詢沒有關(guān)系,互不影響。而關(guān)聯(lián)子查詢的執(zhí)行依賴于外部查詢,通常情況下都是因?yàn)樽硬樵冎械谋碛玫搅送獠康谋?,并進(jìn)行了條件關(guān)聯(lián),因此每執(zhí)行一次外部查詢,子查詢都要重新計算一次。

從連接類型來看,使用not in與not exists子查詢構(gòu)造的語句都屬于反連接,為了控制連接順序與連接方式,這種反連接經(jīng)常被改寫為外連接,t1 與t2使用左外連接,條件加上右表t2的連接列 is null,也就是左外連接時沒有關(guān)聯(lián)上右表的數(shù)據(jù),表達(dá)了這個含義“t1表中c2列值在t2表的c2列值中不存在的記錄”。反連接改寫為外連接,不會導(dǎo)致關(guān)聯(lián)結(jié)果集放大,因?yàn)闆]有關(guān)聯(lián)上的t1表數(shù)據(jù)只顯示1條,半連接改為內(nèi)連接時要注意去重。外連接語句如下所示:


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

所以本質(zhì)表達(dá)含義上,上面的三條語句都等價。

下面看一下三條語句的執(zhí)行結(jié)果:


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)

可以看出就目前的數(shù)據(jù),三條語句執(zhí)行結(jié)果是相同的。

下面向子查詢的t2中插入一條c2列為null的記錄。


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

再觀察一下三條語句的執(zhí)行結(jié)果:


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表示的關(guān)聯(lián)子查詢與 外連接方式表達(dá)的兩條語句結(jié)果相同,而not in表示的非關(guān)聯(lián)子查詢的結(jié)果集為空。這是因?yàn)樽硬樵僺elect t2.c2 from t2 查詢結(jié)果含有NULL值導(dǎo)致的。NULL屬于未知值,無法與其他值進(jìn)行比較,無從判斷,返回最終結(jié)果集為空。這一點(diǎn)在MySQL與Oracle中返回結(jié)果都是一致的。如果想表達(dá)最初的含義,需要將子查詢中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值的記錄后,結(jié)果集會怎樣呢,兩個表都存在c2列為NULL的值數(shù)據(jù),那么t1表這條NULL值數(shù)據(jù)能否出現(xiàn)在最終結(jié)果集中呢?


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)

從執(zhí)行結(jié)果來看,使用not in非關(guān)聯(lián)子查詢,其執(zhí)行結(jié)果與其他兩條語句的執(zhí)行結(jié)果還是不同,因?yàn)閠1.c2 使用not in在參與比較時就隱含了t1.c2 is not null的含義,所以最終結(jié)果集中不含(3,NULL)這條數(shù)據(jù)。

而not exists關(guān)聯(lián)子查詢,在將外查詢的NULL值傳遞給內(nèi)查詢時執(zhí)行子查詢 select * from t2 where t2.c2=NULL,子查詢中找不到記錄,所以條件返回false, 表示not exists 為true,則最終結(jié)果集中含(3,NULL)這條記錄。

左外left join 與 not exists相同,左表的NULL值在右表中關(guān)聯(lián)不上數(shù)據(jù),所以要返回(3,NULL)這條數(shù)據(jù)。這里要注意NULL 不等于 NULL。


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

說到這里,GreatSQL支持<=>安全等于這個符號,用來判斷NULL值:當(dāng)兩個操作數(shù)均為NULL時,其返回值為1而不為NULL;而當(dāng)一個操作數(shù)為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 子查詢中的=  換成 <=>  時,最終結(jié)果集中去除了(3,NULL)這條數(shù)據(jù)。


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)

注意,一般表關(guān)聯(lián)時不使用<=>安全等于這個符號,想象一下,如果關(guān)聯(lián)的兩個表在關(guān)聯(lián)字段上都存在很多NULL記錄,關(guān)聯(lián)后的結(jié)果集對NULL記錄的關(guān)聯(lián)是以笛卡爾積的形式體現(xiàn)的,嚴(yán)重影響效率,嚴(yán)格來說關(guān)聯(lián)字段都為NULL值不能算作能匹配上。

結(jié)論

使用not in 的非關(guān)聯(lián)子查詢注意NULL值對結(jié)果集的影響,為避免出現(xiàn)空結(jié)果集,需要子查詢中查詢列加 is not null條件將NULL值去除。

實(shí)際使用時注意:需求表達(dá)的含義是否要將外查詢關(guān)聯(lián)字段值為NULL的數(shù)據(jù)輸出,not in隱含了不輸出。

一般認(rèn)為not exists關(guān)聯(lián)子查詢與外連接語句是等價的,可以進(jìn)行相互改寫。

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;

如果不需要輸出外查詢中關(guān)聯(lián)字段為NULL值的數(shù)據(jù),還需再加條件 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)等價了。

責(zé)任編輯:武曉燕 來源: GreatSQL社區(qū)
相關(guān)推薦

2012-11-19 10:30:08

win8

2022-12-02 08:47:36

2020-01-09 08:00:29

微信實(shí)用功能移動開發(fā)

2022-06-01 00:10:24

惡意軟件欺詐行為設(shè)備

2019-11-08 09:52:55

程序員技能開發(fā)者

2015-05-26 09:35:29

運(yùn)維運(yùn)維危機(jī)云計算應(yīng)用

2009-06-12 12:37:38

Java軟件

2020-04-14 08:46:47

Java對象編譯器

2022-07-11 08:48:52

業(yè)務(wù)轉(zhuǎn)型CIO

2019-09-26 08:00:00

AI人工智能

2018-03-15 15:09:29

iOS圖片標(biāo)記蘋果

2018-10-25 15:04:22

編程程序員陷阱

2011-06-28 09:16:11

切克簽到

2021-09-03 06:46:34

MyBatis緩存后端

2021-07-29 18:46:52

可視化類型圖形化

2021-05-12 13:38:47

云計算

2022-05-16 08:09:45

前端API

2024-11-14 10:57:41

vue子組件props

2022-09-09 10:15:06

OAuthJava

2021-10-11 20:22:27

JWT瀏覽器接口
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號