使用"not in"子查询编写select语句的最有效方法是什么?

Sti*_*imy 14 sql t-sql sql-server

编写类似于下面的select语句的最有效方法是什么.

SELECT *
FROM Orders
WHERE Orders.Order_ID not in (Select Order_ID FROM HeldOrders)
Run Code Online (Sandbox Code Playgroud)

要点是当项目不在另一个表中时,您需要来自一个表的记录.

Qua*_*noi 21

对于初学者来说,链接到我博客中关于NOT IN谓词如何工作的旧文章SQL Server(以及其他系统中):


您可以按如下方式重写它:

SELECT  *
FROM    Orders o
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    HeldOrders ho
        WHERE   ho.OrderID = o.OrderID
        )
Run Code Online (Sandbox Code Playgroud)

但是,大多数数据库都会将这些查询视为相同.

这两个查询都会使用某种形式ANTI JOIN.

SQL Server如果要检查两列或更多列,这非常有用,因为SQL Server它不支持此语法:

SELECT  *
FROM    Orders o
WHERE   (col1, col2) NOT IN
        (
        SELECT  col1, col2
        FROM    HeldOrders ho
        )
Run Code Online (Sandbox Code Playgroud)

但请注意,NOT IN由于它处理NULL值的方式,这可能会很棘手.

如果Held.Orders是空的,没有记录发现,子查询的回报,但单一的NULL,整个查询将返回什么(两者INNOT IN会评估对NULL在这种情况下).

考虑这些数据:

Orders:

OrderID
---
1

HeldOrders:

OrderID
---
2
NULL
Run Code Online (Sandbox Code Playgroud)

这个查询:

SELECT  *
FROM    Orders o
WHERE   OrderID NOT IN
        (
        SELECT  OrderID
        FROM    HeldOrders ho
        )
Run Code Online (Sandbox Code Playgroud)

什么都不会返回,这可能不是你所期望的.

但是,这一个:

SELECT  *
FROM    Orders o
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    HeldOrders ho
        WHERE   ho.OrderID = o.OrderID
        )
Run Code Online (Sandbox Code Playgroud)

将返回行OrderID = 1.

请注意,LEFT JOIN其他人提出的解决方案远非最有效的解决方案.

这个查询:

SELECT  *
FROM    Orders o
LEFT JOIN
        HeldOrders ho
ON      ho.OrderID = o.OrderID
WHERE   ho.OrderID IS NULL
Run Code Online (Sandbox Code Playgroud)

将使用过滤条件,需要评估和过滤掉所有匹配的

一个ANTI JOIN双方使用的方法INEXISTS将只需要确保记录不存在,一旦每中的每一行Orders,因此将首先消除所有可能的重复:

  • NESTED LOOPS ANTI JOIN并且MERGE ANTI JOIN在评估时将跳过重复项HeldOrders.
  • A HASH ANTI JOIN将在构建哈希表时消除重复.


Dav*_*ile 8

"最有效"将根据表大小,索引等而有所不同.换句话说,它会根据您使用的具体情况而有所不同.

根据具体情况,我通常使用三种方法来完成您想要的任务.

1.如果对Orders.order_id编制索引,并且HeldOrders相当小,那么您的示例工作正常.

另一种方法是"相关子查询",它与你所拥有的略有不同......

SELECT *
FROM Orders o
WHERE Orders.Order_ID not in (Select Order_ID 
                              FROM HeldOrders h 
                              where h.order_id = o.order_id)
Run Code Online (Sandbox Code Playgroud)

注意添加where子句.当HeldOrders具有大量行时,这往往更好地工作.Order_ID需要在两个表中编入索引.

我有时使用的另一种方法是留下外连接......

SELECT *
FROM Orders o
left outer join HeldOrders h on h.order_id = o.order_id
where h.order_id is null
Run Code Online (Sandbox Code Playgroud)

当使用左外连接时,当匹配行时,h.order_id将在其中匹配o.order_id.如果没有匹配的行,则h.order_id将为NULL.通过检查where子句中的NULL值,您可以过滤掉没有匹配项的所有内容.

这些变化中的每一个在各种场景中可以或多或少地有效地工作.


pjp*_*pjp 5

您可以使用并在右表上LEFT OUTER JOIN检查。NULL

SELECT O1.*
FROM Orders O1
LEFT OUTER JOIN HeldOrders O2
ON O1.Order_ID = O2.Order_Id
WHERE O2.Order_Id IS NULL
Run Code Online (Sandbox Code Playgroud)

  • 我的立场是正确的——一点点 Control-L 动作已经证实了我的这一点。我的想法是 WHERE 子句中的任何内容总是为每一行执行一次,但看来我错了。我认为确定它的最佳方法是始终检查执行计划。 (2认同)