【问题标题】:SQL Server Query - Weird BehaviourSQL Server 查询 - 奇怪的行为
【发布时间】:2016-04-01 01:49:15
【问题描述】:

考虑下面的 SQL。

   SELECT DISTINCT bvc_Order.ID,
            bvc_OrderItem.ProductID,
            bvc_OrderItem_BundleItem.ProductID
FROM dbo.bvc_OrderItem WITH (nolock)
RIGHT OUTER JOIN dbo.bvc_Order WITH (nolock)
LEFT OUTER JOIN dbo.bvc_User WITH (nolock) ON dbo.bvc_Order.UserID = dbo.bvc_User.ID
LEFT OUTER JOIN dbo.Amazon_Merchants WITH (nolock) ON dbo.bvc_Order.CompanyID = dbo.Amazon_Merchants.ID ON dbo.bvc_OrderItem.OrderID = dbo.bvc_Order.ID
LEFT OUTER JOIN dbo.bvc_OrderItem_BundleItem WITH (nolock) ON dbo.bvc_OrderItem.ID = dbo.bvc_OrderItem_BundleItem.OrderItemID
LEFT OUTER JOIN dbo.bvc_Product WITH (nolock) ON dbo.bvc_OrderItem.ProductID = dbo.bvc_Product.ID
WHERE 1=1
AND (bvc_Order.StatusCode <> 1
   AND bvc_Order.StatusCode <> 999)
AND ( bvc_OrderItem.ProductID IN ('28046_00')
   OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00'))
AND bvc_Order.OrderSource = 56;

当我对我的数据库执行查询时,它返回 85 行。嗯,这是不正确的。 如果我只是删除部分“AND bvc_Order.OrderSource = 56”,它会返回 5 行,这确实是正确的。 奇怪的..... 另一件事,如果我删除部分

OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')

即使使用 bvc_Order.OrderSource 过滤器,它也会按预期返回 5 行。 我不确定为什么在我尝试使用过滤器减少行时它会添加更多行。

表 bvc_OrderItem_BundleItem 不包含结果订单 ID 或 OrderItemID 的任何行

[编辑]

谢谢大家,我尝试删除 LEFT/RIGHT Join Mix,但查询管理器不仅允许 LEFT,它确实添加了至少一个 RIGHT 连接。我更新了 SQL 以删除额外的表,现在我们只有三个。但是结果一样

SELECT DISTINCT dbo.bvc_Order.ID, dbo.bvc_OrderItem.ProductID, dbo.bvc_OrderItem_BundleItem.ProductID AS Expr1
FROM         dbo.bvc_OrderItem 
LEFT OUTER JOIN dbo.bvc_OrderItem_BundleItem ON dbo.bvc_OrderItem.ID = dbo.bvc_OrderItem_BundleItem.OrderItemId 
RIGHT OUTER JOIN dbo.bvc_Order ON dbo.bvc_OrderItem.OrderID = dbo.bvc_Order.ID

WHERE 1=1
AND (bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999)
AND ( 
    bvc_OrderItem.ProductID IN ('28046_00')
   OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
  )
AND bvc_Order.OrderSource = 56;

[编辑]到目前为止,还没有解决方案。我之前在我的评论中粘贴了一个链接,其中包含带有查询的有效/无效结果的示例数据输出。又来了。 http://sameers.me/SQLIssue.xlsx

这里要记住的一件事是,ALL left join 是不可能的。让我进一步解释 bvc_Order 包含主订单记录 bvc_ORderItem 包含订单项目/产品 bvc_ORderItem_BundleItem 包含产品的子产品,在 bvC_OrderItem 表中可用。

现在不是每个产品都有子产品,所以 bvc_OrderItem_BundleItem 可能没有任何记录(在当前场景中,bvC_OrderItem_BundleItem 中的订单确实没有有效行)。 简而言之,在当前情况下,bvc_OrderItem_BundleItem 表中没有可用的匹配行。如果我现在删除那个连接,一切都好,但在现实世界中,我当然不能删除那个 BundleItem 表连接。

谢谢

【问题讨论】:

  • 在查询中混合左右外连接只是令人困惑。您应该首先重写from 子句以仅使用一种类型(我强烈推荐left outer join)。
  • 摆脱现在(过去 10 年开始)不好的做法(nolock)。编写并格式化它,并在SQL Fiddle 中放置一个带有数据的示例,这样我们就可以找到这里发生的事情。根据我的经验,奇怪的行为来自糟糕的书面查询,引擎只是尝试按照您的指示去做。
  • 使用WITH (NOLOCK) 可能会提供微不足道的性能提升,但您的查询结果将不可靠。
  • 我不明白您为什么不能更改自己的代码(删除右连接)?你不是在编写这个 SQL 查询吗?
  • 请在评论中运行您的“使用所有左连接的更新查询”,并将 5 条正确的结果记录和 85 条错误的结果记录发布到您的问题中。其他 80 条记录不正确的原因是什么?

标签: sql sql-server sql-server-2008


【解决方案1】:

当你说

WHERE bvc_Order.OrderSource = 56

bvc_Order.OrderSourceNULL 时,其计算结果为假。如果LEFT/RIGHT 加入失败,那么它将是NULL。这有效地将LEFT/RIGHT 连接变为内部连接。

您可能应该将谓词写入ON 子句。另一种可能不会产生相同结果的方法是:

WHERE (bvc_Order.OrderSource IS NULL OR bvc_Order.OrderSource = 56)

其他谓词也有同样的问题:

另一件事,如果我删除部分 OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00') 它也会按预期返回 5 行

当连接失败时bvc_OrderItem_BundleItem.ProductIDNULL

我还建议手动编写查询。如果我理解正确,这个查询来自设计师。它的结构相当混乱。我提出最重要的评论:

在查询中混合左外连接和右外连接只是令人困惑。您应该首先重写 from 子句以仅使用一种类型(我强烈建议使用左外连接)。 ——戈登·林诺夫

【讨论】:

    【解决方案2】:

    当你消除了所有不可能的事情之后,剩下的一切,然而 不可能,一定是真的吗? S.H.

    附加到 WHERE 子句的额外 AND 条件不可能导致额外的行。这意味着数据库引擎缺陷,我希望我可以假设这是“不可能的”。 (如果不是,那我猜它又回到了原点)。

    这一事实使我们更容易专注于可能的原因:

    1. 当你注释掉

      AND bvc_Order.OrderSource = 56;
      

      然后您还注释掉分号终止符。可能吗 这个查询后面有影响它的文本吗?尝试 在上一行的末尾放一个分号以确保。

    2. 取决于您用于运行查询的工具,有时当 查询无法执行,工具错误地显示旧的结果集。 通过添加虚拟列确保您的查询正确执行 到 SELECT 语句以绝对证明您正在观看直播 结果。你用的是什么工具?

    【讨论】:

      【解决方案3】:

      当您使用左外连接时,一旦您的 and 或条件满足,它将提供左表 (dbo.bvc_OrderItem) 中的所有行, 右外连接也会发生同样的事情,

      这些条件(左连接,右连接)可能不会限制行,因为一个表中的行可以是全部,另一个表只有一些行。

      检查您的加入条件

      然后检查你的情况:
      (bvc_Order.StatusCode 1 AND bvc_Order.StatusCode 999) 如果有满足此条件的行 下一次检查另一个条件 [bvc_OrderItem.ProductID IN ('28046_00') 或 bvc_OrderItem_BundleItem.ProductID IN ('28046_00')]

      然后 bvc_Order.OrderSource = 56

      比较三个查询的结果,并根据条件检查数据,然后编写完整的查询,这样你就会明白你在哪里犯了错误。

      【讨论】:

        【解决方案4】:

        需要记住的几点
        1.And 在虚拟连接阶段应用
        2.Where子句在最终结果之后应用
        3.在某些情况下,左连接后右连接实际上是内连接

        让我们逐步打破您的查询..

        dbo.bvc_OrderItem  a1
        LEFT OUTER JOIN 
        dbo.bvc_OrderItem_BundleItem b1
        

        上面的输出将是一个单一的虚拟表(逻辑上),其中包含来自 b1 的所有行以及来自 a1 的匹配行

        现在将应用 and 子句中的以下谓词

        bvc_OrderItem.ProductID IN ('28046_00')
           OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
        

        这有效地消除了 bvc_OrderItem_BundleItem 中的所有行,即使它们有匹配项,如果 bvc_OrderItem_BundleItem.ProductID IN ('28046_00') 为真,则会给出如下所示的结果

        bvc_OrderItem   bvc_OrderItem_BundleItem
        
        28046             28046
        null              1
        null              2
        null              3
        

        如果这个条件(bvc_OrderItem.ProductID IN ('28046_00')) 为真,那么您要求 sql 忽略 bvc_OrderItem 中的所有行,这实际上意味着与上述相同的结果集

        bvc_OrderItem   bvc_OrderItem_BundleItem  other columns
        
        28046             28046
        null              1
        null              2
        null              3
        

        接下来您将使用 dbo.bvc_Order 进行右外连接,这可能符合我上面提到的连接点

        假设,您得到以下结果集作为输出,它保留了所有 bvc_order 表(由于缺乏实际数据,粗略输出仅供理解)

        bvc_OrderItem   bvc_OrderItem_BundleItem  statuscode  ordersource
        28046             28046                    999         56
        null              1                        1           57
        null              2                       100          58
        null              3                        11          59
        

        接下来将应用 AND 谓词

        状态码1和状态码999

        这意味着忽略与 bvc_order 匹配且状态为 1,999 的行,即使它们找到匹配的行

        接下来你要问的是 bvc_Order.OrderSource = 56;这意味着我不关心其他行,只为 56 保留匹配的行,其余的保留为 null

        希望这可以逐步阐明正在发生的事情。更好的方法是提供一些测试数据并显示预期的输出。

        你也可以控制连接的物理顺序,你可以试试下面看看这是否是你想要做的..

        SELECT DISTINCT dbo.bvc_Order.ID, dbo.bvc_OrderItem.ProductID, dbo.bvc_OrderItem_BundleItem.ProductID AS Expr1
         dbo.bvc_OrderItem 
        LEFT OUTER JOIN 
        (
        dbo.bvc_OrderItem_BundleItem 
        RIGHT OUTER JOIN 
        dbo.bvc_Order 
        ON dbo.bvc_OrderItem.OrderID = dbo.bvc_OrderItem_BundleItem.OrderItemId
        )c
        on 
        dbo.bvc_OrderItem.ID = c.bvc_OrderItem_BundleItem.OrderItemId 
        
        WHERE 1=1
        AND (bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999)
        AND ( 
            bvc_OrderItem.ProductID IN ('28046_00')
           OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
          )
        AND bvc_Order.OrderSource = 56;
        

        【讨论】:

          【解决方案5】:

          您似乎正在使用查询设计器。我会避免使用它,因为这会使您的查询非常混乱。如果您手动设计查询,您的查询将更加简洁。如果您不完全了解内部/外部联接的工作原理,我用来自学 SQL 的一本很棒的教科书是 Murach 的 SQL Server for Developers。

          https://www.murach.com/shop/murach-s-sql-server-2012-for-developers-detail

          现在,进入答案。

          我一直在考虑如何解决您的问题,如果您想将结果集减少到 5 行,为什么要首先使用多个外连接?如果您正在寻找一个非常具体的结果集,我会考虑将联接切换到内部联接而不是外部联接。如果不确切了解您要达到的结果,我无法真正为您提供真正全面的答案,但以下是基于您提供给我们所有人的总体思路:

          SELECT DISTINCT dbo.bvc_Order.ID, dbo.bvc_OrderItem.ProductID, dbo.bvc_OrderItem_BundleItem.ProductID AS 'bvc_OrderItem_BundleItem_ProductID'
          FROM         dbo.bvc_OrderItem 
          INNER JOIN dbo.bvc_OrderItem_BundleItem ON dbo.bvc_OrderItem.ID = dbo.bvc_OrderItem_BundleItem.OrderItemId 
          INNER JOIN dbo.bvc_Order ON dbo.bvc_OrderItem.OrderID = dbo.bvc_Order.ID    
          

          从这里开始,然后根据您要搜索的内容,添加 where 子句来过滤条件。

          此外,如果使用内连接而不是外连接,则必须重写 where 子句:

          WHERE 1=1 --not really sure why this is here.  This will always be true.  Omit this statement to avoid a bad result set.
          AND (bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999) --this is saying, if the StatusCode is not equal to 1 and not equal to 999, don't include it.  
          --Revised: Look for Status codes with 1 or 999
          --bvc_Order.StatusCode = 1 OR bvc_Order.StatusCode = 999
              AND (bvc_OrderItem.ProductID IN ('28046_00') --I would eliminate this unless you are looking to see if this exists in Product ID.  You could also accomplish this if you are trying to see if this value is in both tables, change this to:
          bvc_OrderItem.ProductID  = '28046_00' AND bvc_OrderItem_BundleItem.ProductID = '28046_00')
          --if you are trying to see if the order source is 56, use this.
              AND bvc_Order.OrderSource = 56;
          

          如果您试图找出未包含在此结果集中的行,那么我会根据需要使用 OUTER JOIN(首选 LEFT)。如果没有关于您在数据库中查找的内容的更多信息,这是我们所有人所能做的最好的事情。

          【讨论】:

          • 与此答案无关,但我无法评论您的原始答案,因为您已将其删除。我很欣赏这本书的推荐。此外,即使明确要求,该网站也禁止推荐书籍,这太愚蠢了。再次感谢。
          • 是的,很抱歉 - 我对 SO 还是很陌生,我收到了删除关于 OAuth 2.0 的答案的请求。很高兴我能帮上忙(虽然有点)!
          • 您好,这种全左连接是不可能的,因为正如我之前解释的,如果存在于 bvc_OrderItem 或 bvc_OrderItem_BundleItem 中,我必须检查并选择行。如果这两个表中的任何一个包含 PRoductID,我需要从 bvc_Order 和 bvc_OrderItem 中选择行。请记住,如果 bvc_OrderItem_BundleItem 中存在具有 OrderID 的行,则 bvc_OrderItem 中始终存在具有相同 OrderID 的行。但是 bvc_ORderItem 和 bvc_ORderItem_BundleItem 中的 ProductID 不同。
          • 感谢您对我的回答发表评论。我还没有足够的代表在 cmets 中提问,所以这有助于我做出回应。如果您正在寻找存在于两个表中的东西,那么您肯定是在查看内部联接——而不是外部联接。如果您邀请我与您聊天,我可以为您提供更多帮助。我可以尝试帮助您解决那里的问题。
          【解决方案6】:

          b 像@usr writ 一样,这个意外(对您而言)结果的原因是,您使用外连接构建查询,并在连接后过滤行。如果您需要过滤外部联接表的行,则应在联接之前执行此操作。
          但可能你尝试构建这个:

          SELECT DISTINCT o.ID, oi.ProductID, bi.ProductID AS Expr1
          FROM dbo.bvc_Order as o       
          LEFT JOIN dbo.bvc_OrderItem as oi on oi.OrderID = o.ID
          LEFT JOIN dbo.bvc_OrderItem_BundleItem as bi ON oi.ID = bi.OrderItemId 
          WHERE 1=1
          AND o.OrderSource = 56;
          AND o.StatusCode not in (1, 999)
          AND '28046_00' in (oi.ProductID, isnull(bi.ProductID,'_') )
          

          此查询是否提供您需要的结果? 如果不是,请尝试更改最后一个条件,例如:

          and (bi.ProductID = '28046_00' or bi.ProductID is null and oi.ProductID = '28046_00')
          

          您还可以在加入条件中添加附加条件,例如:

          SELECT DISTINCT o.ID, oi.ProductID, bi.ProductID AS Expr1
          FROM dbo.bvc_Order as o       
          LEFT JOIN dbo.bvc_OrderItem as oi on oi.OrderID = o.ID
          LEFT JOIN dbo.bvc_OrderItem_BundleItem as bi ON oi.ID = bi.OrderItemId
          and bi.ProductID in ('28046_00') --this join BundleItem only if ...
          WHERE 1=1
          AND o.OrderSource = 56;
          AND o.StatusCode not in (1, 999)
          AND (oi.ProductID in ('28046_00') or bi.ProductID is not null)
          

          啊,如果您总是需要将 bvc_Order 与 bvc_OrderItem 连接,请使用内部连接

          【讨论】:

          • 您好,这种全左连接是不可能的,因为正如我之前解释的那样,如果存在于 bvc_OrderItem 或 bvc_OrderItem_BundleItem 中,我必须检查并选择行。如果这两个表中的任何一个包含 PRoductID,我需要从 bvc_Order 和 bvc_OrderItem 中选择行。请记住,如果 bvc_OrderItem_BundleItem 中存在具有 OrderID 的行,则 bvc_OrderItem 中始终存在具有相同 OrderID 的行。但是 bvc_ORderItem 和 bvc_ORderItem_BundleItem 中的 ProductID 不同
          • 您是否测试了我的查询?是否有任何错误或不良结果?这不是你的左连接......这个连接就像: bvc_Order
          猜你喜欢
          • 1970-01-01
          • 2016-02-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-26
          • 1970-01-01
          相关资源
          最近更新 更多