如何在 SQl 服务器中使用 left outer join?

How to work left outer join in SQl Server?

首先:我知道可以使用所有类型的连接,但是我不知道为什么这个查询会这样工作

我有一个用于进行 SQL 查询的方案,方法是使用 3 个表以及销售和订购商品之间的左外连接。

我的表格:

--------------------
   Item 
--------------------
ID      |  Code
--------------------
1       |  7502

SQL > select * from Item where id = 1
---------------------

   Item_Order
---------------------------
Item   |  Box   |   Quantity
---------------------------
1      | 30     |  15000
1      | 12     |  6000
SQL > select * from Item_Order where Item = 1
--------------------------

   Invoice_Item
-------------------
Item  |  Num  |  Quantity
-------------------------
1     | 1.64  | 10
1     | 2.4   | 8
SQL > select * from Invoice_Item where Item = 1

我想要这个输出:

Item  | OrderQ  | OrderB | SellN | SellQ
-----------------------------------------
1     | 1500    | 30     |  1.64 | 10
1     | 6000    | 12     |  2.4  | 8

我的SQL代码:

SELECT  Item.ID, Item_Order.Box As OrderB, Item_Order.Quantity As OrderQ, Invoice_Item.Num As SellN, Invoice_Item.Quantity As SellQ
FROM Item LEFT OUTER JOIN 
     Invoice_Item ON Item.ID = Invoice_Item.Item LEFT OUTER JOIN 
     Item_Order ON Item_Order.Item = Item.ID  
where Item.ID = 1

为什么我的输出是 2x?或者为什么我的输出 return 4 条记录?

这是重复的,因为您在 Invoice_Item 和 Item_Order 之间没有二级关联。对于 Invoice_Item 中的每条记录,它仅根据项目 ID 匹配 Item_Order(称为笛卡尔结果)。因此,您的订单数量似乎是一个 1:1 参考,因此第一个发票项目数量 10 意味着与 Item_Order Box = 30 相关联。而数量 8 意味着与 Item_Order 盒子 = 12.

Item_Order
Item   Box   Quantity
1      30    15000
1      12    6000


Invoice_Item
Item  Num    Quantity
1     1.64   10
1     2.4    8

您可能需要添加 "Box" 参考,因此 Item_Order 和 Invoice_Item 是 1:1 匹配项。

发票项目中的每个项目都根据项目 ID 连接到 Item_Order。所以你得到两个。如果您有 3 个发票项目,其中 1 个和 6 个 Items_Order,您将得到 18 行。

反馈

即使您有一个基于 OVER/PARTITION/ROW NUMBER 的可接受答案,该过程也会强制为每一行提供一个替代辅助 ID。依赖这种方法对于整体数据结构关联来说并不是最好的。如果您删除订单中的第二个项目会发生什么。您确定要删除 invoice_items 中的第二项吗?

至于在原始场景中 returning 2 条记录,您可以通过代理过程,但我认为从长远来看,了解连接上发生的事情对您来说会更好。回到 Item_Order 和 Invoice_Item 的样本数据。那么让我们从 Item_Order table 开始。 SQL 引擎将单独处理每一行。

第一行 SQL 抓取 Item = 1,Box = 30,Qty = 15000。

所以现在它加入了发票项目 table,并且由于您的标准它只根据项目加入。因此,它看到第一行并说...是的,这是第 1 项,因此请将其包含在项目订单记录中(第一行 returned)。现在它转到发票项目的第二行 table... 是的,它也是同一个项目 1,所以它再次 return 了(第二行 returned)。

现在,SQL 抓取第二行 Item = 1,Box = 12,Qty = 6000。

返回发票项目 table 并进行完全相同的测试...并且对于项目订单中项目 = 1 的每一行,以及第 3 行和第 4 行,因此您加倍...如果 table 中有更多记录具有相同的项目 ID,则将 return 更多记录...3 和 3 记录将有 return 9 行。 4 和 4 条记录会 return 16 行,等等。做代理会起作用,但我认为不如 better/updated 设计结构安全。

您的结果可以通过 row_number:

实现
select a.ID
       , a.OrderB
       , a.OrderQ
       , b.Quantity SellQ
       , b.Num SellN
from 
(SELECT Item.ID
       , Item_Order.Box As OrderB
       , Item_Order.Quantity As OrderQ
       , row_number () over (order by Item.ID) rn
FROM Item 
left outer JOIN Item_Order ON Item.ID = Item_Order.Item) a
left outer join (select Item
                        , Num
                        , Quantity
                        , row_number () over (order by Item) rn 
                 from Invoice_Item ) b
on a.ID = b.Item
and a.rn = b.rn

Here is a demo

您可以像这样添加更多表格:

left outer join (select Item
                            , Num
                            , Quantity
                            , row_number () over (order by Item) rn 
                     from Invoice_Item ) b

因为当您第一次加入 ItemItem_Order 时,它会输出两条记录,因为 Item_Order 中有两条记录。现在,此结果查询将与 Invoice_Item 左连接,并且两条记录将与 Invoice_Item

的所有记录连接

你可以这样理解更好

SELECT  Item.ID, Item_Order.Box As OrderB, Item_Order.Quantity As OrderQ, Invoice_Item.Num As SellN, Invoice_Item.Quantity As SellQ
FROM Item LEFT OUTER JOIN 
Invoice_Item ON Item.ID = Invoice_Item.Item LEFT OUTER JOIN 
where Item.ID = 1 into table4 //Only to explain

现在第一个查询 table4 的结果将与 Items_Order

合并

您正在按一个键加入 - 两行具有相同的键,其中一个 table 乘以第二个中的两行 table = 4 行。

您需要一个单独的密钥。您可以使用 row_number():

生成一个
SELECT i.ID, io.Box As OrderB, io.Quantity As OrderQ,
       ii.Num As SellN, ii.Quantity As SellQ
FROM Item i LEFT OUTER JOIN 
     ((SELECT ii.*,
              ROW_NUMBER() OVER (PARTITION BY ii.item ORDER BY ii.item) as seqnum
       FROM Invoice_Item ii
      ) FULL JOIN
      (SELECT io.*, 
              ROW_NUMBER() OVER (PARTITION BY io.item ORDER BY io.item) as seqnum
       FROM Item_Order io
      ) io
      ON io.Item = ii.ID AND io.seqnum = ii.seqnum
     )
     ON i. = ii.Item 
where i.ID = 1;

请注意,这是我在 FROM 子句中使用圆括号的少数情况之一。此代码可以处理 table 的 either 中的其他行——如果一个 table 比另一个长,则另一个的列将是 NULL.

如果您知道两个 table 具有相同的行数(对于给定的项目),您可以只使用内部联接而不使用括号。