SQL Server查询 - 奇怪的行为

时间:2016-03-18 10:56:42

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

请考虑以下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行。嗯,这不正确。 如果我只是删除部分&#34; AND bvc_Order.OrderSource = 56&#34;它返回5行,这是非常正确的。 奇怪..... 另一件事,如果我删除部分

OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')

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

表bvc_OrderItem_BundleItem不包含结果订单ID或OrderItemIDs

的任何行

[编辑]

谢谢大家,我试图删除LEFT / RIGHT Join Mix但查询管理器不允许只有LEFT,它确实添加了至少一个RIGHT join。我更新了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

这里要记住的一点是,所有左连接都是不可能的。让我进一步解释 bvc_Order包含主订单记录 bvc_ORderItem包含订单商品/产品 bvc_ORderItem_BundleItem包含产品的子产品,这些产品在bvC_OrderItem表中可用。

现在并非每个产品都有子产品,因此bvc_OrderItem_BundleItem可能没有任何记录(在当前情况下,bvC_OrderItem_BundleItem中的订单实际上没有有效行)。 简而言之,在当前场景中,bvc_OrderItem_BundleItem表中没有可用的匹配行。如果我暂时删除该连接,这一切都没关系,但在现实世界中,我无法删除该BundleItem表连接。

谢谢

6 个答案:

答案 0 :(得分:10)

当你说

WHERE bvc_Order.OrderSource = 56

bvc_Order.OrderSourceNULL时评估为false。如果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子句以仅使用一种类型(我强烈建议使用左外连接)。 - 戈登林诺夫

答案 1 :(得分:1)

  

然而,当你消除了不可能的事物时,无论如何   不太可能,必须是真相? S.H。

附加到WHERE子句的额外AND条件不可能导致额外的行。这意味着数据库引擎缺陷,我希望我可以假设这是不可能的#34; (如果没有,那么我想它会回到正方形)。

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

  1. 当你发表评论时

    AND bvc_Order.OrderSource = 56;
    

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

  2. 取决于您用来运行查询的工具,有时候是在 查询无法执行,该工具错误地显示旧的结果集。 通过添加虚拟列确保您的查询正确执行 到SELECT语句绝对证明你正在看到现场 结果。您使用的是哪种工具?

答案 2 :(得分:0)

当你使用LEFT外连接时,一旦你和/或条件满足,它将给出左表(dbo.bvc_OrderItem)中的所有行 右外连接也会发生同样的事情,

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

检查您的加入条件

然后检查你的状况:
(bvc_Order.StatusCode&lt;&gt; 1 AND bvc_Order.StatusCode&lt;&gt; 999) 如果有任何行满足这个条件 接下来检查另一个条件 [bvc_OrderItem.ProductID IN(&#39; 28046_00&#39;)    或bvc_OrderItem_BundleItem.ProductID IN(&#39; 28046_00&#39;)]

然后bvc_Order.OrderSource = 56

比较三个查询的结果并检查条件中的数据,然后编写完整的查询,以便了解您所犯的错误。

答案 3 :(得分:0)

bLike @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_OrderItem加入bvc_Order然后使用内连接

答案 4 :(得分:0)

几点要记住 1.在虚拟连接阶段应用 2.最终结果后适用的条款
3.Left join然后右连接在某些情况下实际上是内连接

让我们一步一步地打破你的疑问..

dbo.bvc_OrderItem  a1
LEFT OUTER JOIN 
dbo.bvc_OrderItem_BundleItem b1

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

现在来自你的和子句的谓词将被应用

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

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

bvc_OrderItem   bvc_OrderItem_BundleItem

28046             28046
null              1
null              2
null              3

如果这个条件(bvc_OrderItem.ProductID IN(&#39; 28046_00&#39;))为真,那么你要求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谓词

状态代码&lt;&gt; 1和状态代码&lt;&gt; 999

表示忽略与bvc_order匹配且状态为1,999的行,即使他们找到匹配的行

接下来你问的是bvc_Order.OrderSource = 56;这意味着我不关心其他行,仅为56保留匹配行,并将其余行保持为空

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

你也可以控制连接的物理顺序,你可以尝试下面看看你是否正在尝试这样做..

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 :(得分:0)

看起来您正在使用查询设计器。我会避免使用它,因为这可能会使您的查询非常混乱。如果您手动设计它们,您的查询将更加简洁。如果你不完全理解内部/外部联接是如何工作的,那么我用来自学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首选)。如果没有关于您在数据库中寻找什么的更多信息,那就是我们所能做的最好的事情。