NOT IN子查詢中出現NULL值對結果的影響你註意到了嗎

来源:https://www.cnblogs.com/greatsql/p/18074890
-Advertisement-
Play Games

前言 開發人員寫的SQL語句中經常會用到in,exists,not in,not exists 這類子查詢,通常,含in、exists的子查詢稱為半連接(semijoin),含not in、 not exists的子查詢被稱之為反連接,經常會有技術人員來評論in 與exists 效率孰高孰低的問題, ...


前言

開發人員寫的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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述:C#中的Attribute(特性)為程式元素提供了靈活的元數據機制。除基礎應用外,可高級應用於自定義代碼生成、AOP等領域。通過示例展示了Attribute在AOP中的實際用途,以及如何通過反射機制獲取並執行與Attribute相關的邏輯。 在C#中,Attribute(特性)是一種用於為程式 ...
  • 概述:.NET中實現數字轉大寫金額可通過現有庫或自定義方法。自定義方法示例使用遞歸將數字分段轉換為中文大寫金額,處理了千、百、十、個位數。實際應用中可根據需求進一步擴展,例如處理小數部分或負數。 在.NET中,你可以使用以下方案之一來實現將數字轉成大寫金額: 使用現有庫: .NET框架中有一些庫已經 ...
  • (適用於.NET/.NET Core/.NET Framework)【目錄】0.前言1.第一個AOP程式2.Aspect橫切麵編程3.一個橫切麵程式攔截多個主程式4.多個橫切麵程式攔截一個主程式5.優勢總結6.展望 0.前言AOP(Aspect Oriented Programming)是“面向橫切 ...
  • 前幾天,點開自己的博客,看了一下 CYQ.Data V5系列 都有哪些文章,發現了一篇2019年寫的:CYQ.Data 對於分散式緩存Redis、MemCache高可用的改進及性能測試,於是點進去看了看。感覺文章中有些表述存有問題,,不過不是重點。 重點,看了裡面的測試結論,如果四五年過去了,CYQ... ...
  • 之前的“性能優化的一般策略及方法”一文中介紹了多種性能優化的方法。根據以往的項目經驗,開啟編譯器優化選項可能是立竿見影、成本最低、效果最好的方式了。 這麼說可能還不夠直觀,舉個真實的例子:我所參與的自動駕駛的項目中,無需修改任何代碼,僅僅增加一個 -O2 選項,進程整體的 CPU loading 可 ...
  • 伺服器部署項目總結 yzh 24/3/15 歡迎查看CSDN的博文https://blog.csdn.net/AN_NI_112/article/details/136749033?spm=1001.2014.3001.5501 前言 本著一定成功的信心去部署前後端分離的項目,結果卻“路遇坎坷” ...
  • IndexedDB是一種在瀏覽器端存儲數據的方式,它豐富了客戶端的查詢方式,由於是本地存儲,可以有效減少網路對頁面數據的影響。這使得瀏覽器可以存儲更多的數據,從而豐富了瀏覽器端的應用類型。 IndexedDB與傳統的關係型資料庫不同,它是一個key-value型的資料庫。其中,value可以是複雜的 ...
  • 近期看到了一個前阿裡資深開發的學術分析視頻: 高併發情況下,一個事務內有更新操作還有查詢操作,那是先更新好,還是先無鎖查詢好? 僅70秒的視頻,深感學問太深,但是海哥講的有待補充,於是寫下了這篇文章,作為補充。 鳴謝:前阿裡資深開發極海Channel的技術分享。 先說答案 這是個開放性的問題,必須看 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...