原文:开发随笔——NOT IN vs NOT EXISTS

原文出处: http://blog.csdn.net/dba_huangzj/article/details/31374037  转载请引用

之前在论坛中见到一个针对in/exists的讨论,原帖懒得找了,这里介绍一下最近的学习小结:

NOT IN和NOT EIXTS在对允许为null的列查询时会有一定的风险。特别是NOT IN,如果子查询包含了最少一个NULL,会出现非预期的结果。下面做一个演示。

  1. IF OBJECT_ID('ShipmentItems', 'U') IS NOT NULL
  2. DROP TABLE dbo.ShipmentItems;
  3. GO
  4. CREATE TABLE dbo.ShipmentItems
  5. (
  6. ShipmentBarcode VARCHAR(30) NOT NULL ,
  7. Description VARCHAR(100) NULL ,
  8. Barcode VARCHAR(30) NOT NULL
  9. );
  10. GO
  11. INSERT INTO dbo.ShipmentItems
  12. ( ShipmentBarcode ,
  13. Barcode ,
  14. Description
  15. )
  16. SELECT '123456' ,
  17. '1010203' ,
  18. 'Some cool widget'
  19. UNION ALL
  20. SELECT '123654' ,
  21. '1010203' ,
  22. 'Some cool widget'
  23. UNION ALL
  24. SELECT '123654' ,
  25. '1010204' ,
  26. 'Some cool stuff for some gadget';
  27. GO
  28. -- retrieve all the items from shipment 123654
  29. -- that are not shipped in shipment 123456
  30. SELECT Barcode
  31. FROM dbo.ShipmentItems
  32. WHERE ShipmentBarcode = '123654'
  33. AND Barcode NOT IN ( SELECT Barcode
  34. FROM dbo.ShipmentItems
  35. WHERE ShipmentBarcode = '123456' );
  36. /*
  37. Barcode
  38. ------------------------------
  39. 1010204
  40. */

可以看出得到了期待结果。下面看看修改表结构,允许列为null的情况:

  1. ALTER TABLE dbo.ShipmentItems
  2. ALTER COLUMN Barcode VARCHAR(30) NULL;
  3. INSERT INTO dbo.ShipmentItems
  4. ( ShipmentBarcode ,
  5. Barcode ,
  6. Description
  7. )
  8. SELECT '123456' ,
  9. NULL ,
  10. 'Users manual for some gadget';
  11. GO
  12. SELECT Barcode
  13. FROM dbo.ShipmentItems
  14. WHERE ShipmentBarcode = '123654'
  15. AND Barcode NOT IN ( SELECT Barcode
  16. FROM dbo.ShipmentItems
  17. WHERE ShipmentBarcode = '123456' );
  18. /*
  19. Barcode
  20. ------------------------------
  21. */

很多人会觉得这是一个bug,有时候能查出数据,有时候却不能。但是实际上不是bug,当NOT IN子句返回最少一个NULL时,查询会返回空,下面的语句能更好地说明这个想法:

  1. SELECT CASE WHEN 1 NOT IN ( 2, 3 ) THEN 'True'
  2. ELSE 'Unknown or False'
  3. END ,
  4. CASE WHEN 1 NOT IN ( 2, 3, NULL ) THEN 'True'
  5. ELSE 'Unknown or False'
  6. END;
  7. /*
  8. ---- ----------------
  9. True Unknown or False
  10. */

实际上,由于IN的本质是OR操作,所以:

  1. SELECT CASE WHEN 1 IN ( 1, 2, NULL ) THEN 'True'
  2. ELSE 'Unknown or False'
  3. END ;

中,1 in 1,也就是为TRUE,所以返回true,这个语句的逻辑实际上是:

  1. SELECT CASE WHEN ( 1 = 1 )
  2. OR ( 1 = 2 )
  3. OR ( 1 = NULL ) THEN 'True'
  4. ELSE 'Unknown or False'
  5. END ;

当使用NOT IN 时,如下面的语句:

  1. SELECT CASE WHEN 1 NOT IN ( 1, 2, NULL ) THEN 'True'
  2. ELSE 'Unknown or False'
  3. END ;

会转变成:

  1. SELECT CASE WHEN NOT ( ( 1 = 1 )
  2. OR ( 1 = 2 )
  3. OR ( 1 = NULL )
  4. ) THEN 'True'
  5. ELSE 'Unknown or False' END ;

根据离散数学的概念,可以转换为:

  1. SELECT CASE WHEN ( ( 1 <> 1 )
  2. AND ( 1 <> 2 )
  3. AND ( 1 <> NULL )
  4. ) THEN 'True'
  5. ELSE 'Unknown or False'
  6. END ;

谓词有短路特性,即在AND条件中,只要有一个条件为false,整个条件都为false,而1<>1是为false,所以后面的也不需要判断了,直接返回else部分。即使是1<>null,根据集合论的特性,NULL和实际数据的对比总是返回unknown,所以也是为false。如果你非要用NOT IN ,请确保子查询永远不会有NULL返回。或者需要额外处理去除NULL,比如:

  1. SELECT Barcode
  2. FROM dbo.ShipmentItems
  3. WHERE ShipmentBarcode = '123654'
  4. AND Barcode NOT IN ( SELECT Barcode
  5. FROM dbo.ShipmentItems
  6. WHERE ShipmentBarcode = '123456'
  7. AND Barcode IS NOT NULL ) ;

还有一种方法就是改写语句,用NOT EXISTS来等价替换:

  1. SELECT i.Barcode
  2. FROM dbo.ShipmentItems AS i
  3. WHERE i.ShipmentBarcode = '123654'
  4. AND NOT EXISTS ( SELECT *
  5. FROM dbo.ShipmentItems AS i1
  6. WHERE i1.ShipmentBarcode = '123456'
  7. AND i1.Barcode = i.Barcode );
  8. /*
  9. Barcode
  10. ------------------------------
  11. 1010204
  12. */

另外,基于SARG要求,一般不建议用NOT IN/NOT EXISTS这种反向扫描,避免影响性能。还有一个选择使用IN/EXISTS的要点,就是多列匹配的问题,在T-SQL中,多列同时匹配要用EXISTS,而单列匹配可以用EXISTS/IN。可能可以用其他写法来实现IN的多列匹配,但是一般我个人会选择使用EXISTS来匹配多列。

原文出自:CSDN博客:黄钊吉的博客

开发随笔——NOT IN vs NOT EXISTS的更多相关文章

  1. Kinect开发随笔①——红外扫描仪(Kinect 数据源)

    来源于 MVA 的 快速入门:Kinect for Windows v2 开发 的学习随笔 具体内容为上图所示章节内容 章节内全部代码:GitHub地址点我(链接失效,待补档) <Page &l ...

  2. UWP开发随笔——使用SQLite数据库

    摘要 大多数的app都需要数据存储,在数据存储这方面,强大的windows把app数据分为两种:settings和files,并提供了十分简洁的api,让开发者能够轻松使用.但是在有些场景下,app的 ...

  3. 初学安卓开发随笔之 Intent 用法

    首先,对于安卓开发,目前世界上流行的是使用的是Android studio 2.0 .(hh 学着来呗 书上说用这个,,) 今后就定一个计划 每天更新一个Android 随笔,增强一下自控力吧!!! ...

  4. FPGA开发随笔汇总

    点击标题即可进入相关随笔. DE-SOC开发板VrilogHDL开发相关部分: (本过程需要Verilog HDL 的基本语言基础) 1.FPGA的发展史及FPGA 的基础架构 2.首先看一下友晶DE ...

  5. cefSharp 开发随笔

    最近用cefSharp开发一点简单的东西.记录一点随笔,不定时更新. 1.用nuget安装完之后,架构要选择x86或者x64,否则编译会报错(截止到Chrome 55版本) 2.向Chrome注册C# ...

  6. ActiveReport系列报表开发随笔收集

    转自:博客园 http://www.cnblogs.com/dahuzizyd/archive/2007/04/11/ActiveReport_All.html 使用ActiveReport for ...

  7. 如何提高码农产量,基于ASP.NET MVC的敏捷开发框架之移动端开发随笔二

    前言 在前一篇文章中我已经做过开篇,接下来的随笔会详细讲一下我们的开发框架是如何实现的,专业的事由专业的人来讲,以后就由我们的高级码农小李英文名查尔斯和他的师父厂长(因为姓陈,酷爱摄影,我们的文艺片都 ...

  8. com.panie 项目开发随笔(NoF)_环境搭建(2016.12.29)

    (一) 最近做的框架一直在 spring + springmvc + mybatis 的基础上,使用框架的好处自然是 简化了自己的开发工作,定义好大的结构体系后就在里面套用方法了! 可是框架的毛病同样 ...

  9. com.panie 项目开发随笔_前后端框架考虑(2016.12.8)

    (一) 近日和一同学联系,说了我想要做一个网站的打算.她很感兴趣.于是我们协商了下,便觉得一起合作.她写前端,我写后台.因为我对于前端样式设计并不怎么熟悉. (二) 我们决定先做一个 个人博客. 网上 ...

随机推荐

  1. Windows Server时间服务器配置方法

    1 时间服务器经常会碰到客户端机器需要和服务器在时间上保持同步,否则会出现各种问题,特别是有时间相关的触发功能的时候. 为解决各设备间时间统一的问题,我们可在网络中设置一台服务器使其作为基准时间,其它 ...

  2. Android HAL

  3. Eclipse 快捷键整理

    Alt+/:代码提示Ctrl+/:注释/取消注释Ctrl+D:删除光标所在行Ctrl+K:将光标停留在变量上,按Ctrl+K键可以查找到下一个同样的变量Shift+Ctrl+K:和Ctrl+K查找的方 ...

  4. HDU 4380 Farmer Greedy 计算几何+bitset

    枚举直线,对于直线的某个点在直线的左端还是右端,能够状压出一个数.用bitset记录. 然后三角形就是3个bitset&一下 #include <cstdio> #include ...

  5. Jsoup 抓取和数据页 认识HTTP头

    推荐一本书:黑客攻防技术宝典.Web实战篇  :       顺便留下一个疑问:能否通过jsoup大量并发訪问web或者小型域名server,使其瘫痪?其有用jsoup熟悉的朋友能够用它解析url来干 ...

  6. 使用psftp.exe

    使用psftp.exe 点击打开psftp.exe,出现如下图的命令窗口.  

  7. QVector&lt;QString&gt; 显示器里面的动态数组元素QString和char *转变

    QVector类是一类提供了动态数组模板. QVector<T>是Qt普通容器类的一种. 它将自己的每个对象存储在连续的内存中.能够使用索引號来高速訪问它们.QList<T>. ...

  8. Ubuntu14.04 用 CrossOver 安装 TMQQ2013

    Crossover 是 wine 的优化+商业版本号 ,  免去了wine的繁琐配置,让Ubuntu安装windows软件很easy..... 部分移植的软件还有官方的维护,执行效果也比較好..... ...

  9. crm2011js子网格导航栏字段事件操作

  10. jQuery回到顶部插件jQuery GoUp

    插件描写叙述 jQuery GoUp!是一个简单的jQuery插件,让你的网页用户直接回到顶部. 用法很easy 引用jquery库和jquery.goup.min.js到你的页面 <scrip ...