SQL Full Outer Join 重复问题

SQL Full Outer Join duplicate Issue

我已经对此进行了相当多的搜索,但我就是看不出我哪里出错了。我希望有人能帮我弄清楚。我有两个 table,一个用于一个零件号的所有销售订单 (SO),一个用于一个零件号的所有采购订单 (PO)。我想将结果合并在一起。大多数时候,每个零件的采购订单和销售订单数量不同。在此示例中,我有 2 个销售订单和 1 个采购订单。

Table 1 (SO)
Company     Part     SalesOrder
ABC         123       5530
ABC         123       6854
ABC         456       7772
ABC         456       6868

Table 2 (PO)
Company     Part     PurchaseOrder
ABC         123       9889
ABC         456       9308
ABC         456       9655
ABC         456       9774

我希望看到:

Company     Part     SalesOrder     PurchaseOrder
ABC         123       5530            9889
ABC         123       6854            NULL
ABC         456       7772            9308
ABC         456       6868            9655
ABC         456       NULL            9774

但我看到了:

Company     Part     SalesOrder     PurchaseOrder
ABC         123       5530            9889
ABC         123       6854            9889
ABC         456       7772            9308
ABC         456       7772            9655
ABC         456       7772            9774
ABC         456       6868            9308
ABC         456       6868            9655
ABC         456       6868            9774

这是我的查询:

 select coalesce(SO.Company, PO.Company) as Company,
 coalesce(SO.Part, PO.Part) as Part, 
 SO.SalesOrder, PO.PurchaseOrder 
 from  SO full outer join PO
 on SO.Company=PO.Company and SO.Part=PO.Part

也许我需要的不是完全外部连接?作为参考,我看了 posts 之类的 SQL Full Outer Join,我认为我想要的结果看起来与 post 中的结果相似,我认为我的查询看起来像所选的解决方案,但显然我在某处失败了。我非常感谢任何帮助。

----更新---- 我想我造成了一些混乱,为此我深表歉意。澄清一下,PO 和 SO 彼此没有关系,因为它们属于同一部分。不会创建特定的 PO 来实现特定的 SO。有些零件可能只有 SO(例如,制造零件),有些可能只有 PO(制造项目的组件)。有些部分会碰巧具有相同数量的 SO 和 PO,但大多数时候,可能不止一个。例如,如果我想查看某个零件的历史 activity,它可能有 4 个销售订单和 1 个采购订单。如果我要创建一个联合,基本上将 'activity' (SO's/Po's) 聚集到一列中,那么对于该部分,查询将 return 5 行 activity(4 个 SO/1 个 PO)。但是,不是有 1 列和 5 行,我可以让它有 2 列(一列用于 SO,一列用于 PO)并有 4 行吗? SO 列中的所有行都不会为空,对于采购订单,4 行将为空,而 1 行则不会。让第一行包含非空的 PO 行只是一种视觉偏好,但绝不是第一行的 SO 和 PO 实际上相关,除了它们恰好在同一行之外。

举一个完全不同的例子:

假设我有一个客户 table 和一个供应商 table,他们都有 'Name' 和 'State' 的字段名称,我想我在加利福尼亚的所有客户或供应商的列表。我可以做工会吗?

select c.name, 'Cust' as Type, c.state
from customer c 
where c.state='CA'
union
select v.name, 'Vend', v.state
from vendor v
where v.state='CA'

我会得到类似的东西:

Name          Type     State
BB Shrimp     Cust     CA
Vista Inc     Cust     CA
Mary's Lamb   Cust     CA
Cali Coffee   Cust     CA
Cool Guys     Cust     CA
Tap Corp      Vendor   CA
Blue Supply   Vendor   CA
Sun Shore     Vendor   CA

但我想看这个:

Vendor       Customer     State
Tap Corp     BB Shrimp     CA
Blue Supply  Vista Inc     CA
Sun Shore    Mary's Lamb   CA
NULL         Cali Coffee   CA
NULL         Cool Guys     CA
NULL         Tap Corp      CA

我知道你会在哪里问,为什么我会想看到那个,但如果我以这种方式呈现数据,我可以将其放入 SSRS 并使其看起来像

 State   Vendors      Customers  
 CA      Tap Corp     BB Shrimp 
         Blue Supply  Vista Inc                 
         Sun Shore    Mary's Lamb                           
                      Cali Coffee   
                      Cool Guys                

现在切换零件的状态、PO 的供应商和 SO 的客户,这就是我想要实现的目标。供应商和客户除了来自同一州外没有任何关系,某些州的供应商可能比客户多,他们可能相同,但不相关。与 PO 和 SO 的目标相同。

您似乎想根据顺序将 PO 和 SO 配对,但 FULL OUTER JOIN 只会以所有可能的组合将它们配对。如果你想捕获订单,你需要先做一个ROW_NUMBER():

SELECT COALESCE(SO2.company, PO2.company) AS Company,
       COALESCE(SO2.part, PO2.part)       AS Part,
       so.salesorder,
       po.purchaseorder
FROM   (SELECT *,
               Row_number()
                 OVER (
                   partition BY so.part, so.company
                   ORDER BY so.salesorder) AS SO_Sequence
        FROM   so) AS SO2
       FULL OUTER JOIN (SELECT *,
                               Row_number()
                                 OVER (
                                   partition BY po.part, po.company
                                   ORDER BY po.purchaseorder) AS PO_Sequence
                        FROM   po) AS PO2
                    ON SO2.company = PO2.company
                       AND SO2.part = PO2.part
                       AND SO2.so_sequence = PO2.po_sequence 

这就是OUTER JOIN的本质。你的 LEFT table 是你的 SO。对于 SO 中的每一行,您都有一个零件号和订单号,并将在您的结果集中收到一行。计算完所有左侧行后,sql 将计算右侧 table 中的行,因此您会在结果集中为两个 table 集中的每一行获得一行。

你看到的原因

Company     Part     SalesOrder     PurchaseOrder
ABC         123       5530            9889
ABC         123       6854            9889

而不是

Company     Part     SalesOrder     PurchaseOrder
ABC         123       5530            9889
ABC         123       6854            NULL

是因为它在零件号和公司上仍然匹配。

OUTER JOIN 是获得所需结果的正确方法,但您当前的关系模型不够明确,无法正确实现目标。

如果您应该有一个 1::1 关系,您应该在 SO table 中有一个 PO id,或者在 PO table.[=12 中有一个 SO id =]

@Greg Viers 展示了一种方法,可以将 company/part 组合的 PO table 中第一次出现的 PO id 分配给 SO table 中的第一次出现。然而,在几乎每个业务案例中,都可能按照收到的订单执行订单。还有你的订单没有数量吗?您当前的设置在您的数据关系中留下了很多空白。

此外,这在数据方面也更难维护;例如,如果采购订单被取消会怎样?

我只关注了您预期的结果中的第 2 行和第 5 行。我认为 Row 5 expected NULL for SalesOrder 是错误的,我认为应该是 6884 。 这是支持我的主张的逻辑。我不知道你是怎么得到结果的,但我尝试了同样的查询,我得到了你预期的结果

CREATE TABLE #SO ( 
Company NVARCHAR(16),
PART NVARCHAR(16),
SalesOrder NVARCHAR(16) ) 

INSERT INTO #SO VALUES('ABC','123','6854')
INSERT INTO #SO VALUES ('ABC','456','6868')



CREATE TABLE #PO ( 
Company NVARCHAR(16),
PART NVARCHAR(16),
PurchaseOrder NVARCHAR(16) ) 

INSERT INTO #PO VALUES('ABC','456','9308')
INSERT INTO #PO VALUES ('ABC','456','9774')

select * from #SO 
SELECT * FROM #PO


SELECt coalesce(PO.COMPANY, SO.COMPANY) as Company 
     , coalesce(SO.PART, PO.PART) as Part 
     , SO.SalesOrder,po.PurchaseOrder 
      FROM #SO AS SO 
                FULL OUTER JOIN #PO AS PO
                 ON  PO.PART = SO.PART AND 
          PO.Company = SO.Company

您也可以为此使用联盟。我用 union

添加了脚本
SELECT Company, Part, SalesOrder, number = ROW_NUMBER() OVER(Partition By Company,Part ORDER BY Company,Part) 
INTO #TEMP_SO
FROM SO

SELECT Company, Part, PurchaseOrder, number = ROW_NUMBER() OVER(Partition By Company,Part ORDER BY Company,Part) 
INTO #TEMP_PO
FROM PO

SELECT s.Company, s.Part, s.SalesOrder, p.PurchaseOrder FROM #TEMP_SO s
LEFT JOIN #TEMP_PO p ON p.Part = s.Part AND p.Company = s.Company AND p.number = s.number

UNION

SELECT p.Company, p.Part, s.SalesOrder, p.PurchaseOrder FROM #TEMP_PO p 
LEFT JOIN #TEMP_SO s ON p.Part = s.Part AND p.Company = s.Company AND p.number = s.number

DROP TABLE #TEMP_PO
DROP TABLE #TEMP_SO

首先为所有需要的列创建临时 table 并用 insert/update 填充这些列。

在这里我们可以检查行数。如果行数匹配,则更新客户列,否则插入新行。

对于您的客户和供应商示例,您可以使用如下脚本:

-- create the final columns temp table
CREATE TABLE #Temp
(
    ID INT primary KEY identity(1,1),
    Vendors VARCHAR(200),
    Customers VARCHAR(200),
    State VARCHAR(200)
)

-- insert the vendor & state columns
INSERT INTO #Temp (Vendors, Customers,State)
SELECT name, NULL, state  FROM vendor

-- create customer temp table with row numbers
SELECT num = ROW_NUMBER() OVER (ORDER BY name), * 
INTO #Temp_Customer
FROM Customer c

-- update temp table's customer column if the temp tables row number = customer's row number
UPDATE T
SET T.Customers = c.name
FROM #Temp T
INNER JOIN #Temp_Customer C ON c.num = T.ID

-- insert into temp table for customer's record(if customer has row more than the temp table)
INSERT INTO #Temp (Vendors, Customers,State)
SELECT NULL, c.name, c.state
FROM #Temp_Customer C
LEFT JOIN #Temp T ON c.num = T.ID
WHERE T.ID IS NULL


SELECT * FROM #Temp

DROP TABLE #Temp
DROP TABLE #Temp_Customer