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
我已经对此进行了相当多的搜索,但我就是看不出我哪里出错了。我希望有人能帮我弄清楚。我有两个 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