【问题标题】:Joining on a common table, how do you get a FULL OUTER JOIN to expand on another table?在一个普通表上加入,你如何获得一个 FULL OUTER JOIN 来扩展另一个表?
【发布时间】:2011-01-31 20:40:22
【问题描述】:

我已经在 StackOverflow 和 Google 上搜索过这个问题的答案。

我正在尝试创建一个 Microsot SQL Server 2008 视图。不是存储过程。不是函数。只是一个查询(即一个视图)。

我有三张桌子。第一个表定义了一个公共键,比如“CompanyID”。另外两个表有一个有时很常见的字段,比如“EmployeeName”。

我想要一个单表结果,当我的 WHERE 子句显示“WHERE CompanyID = 12”时,该结果如下所示:

CompanyID | TableA    | TableB
12        | John Doe  | John Doe
12        | Betty Sue | NULL
12        | NULL      | Billy Bob

我尝试了如下所示的 FULL OUTER JOIN:

SELECT Company.CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM Company
FULL OUTER JOIN TableA ON Company.CompanyID = TableA.CompanyID
FULL OUTER JOIN TableB ON 
    Company.CompanyID = TableB.CompanyID AND 
    (TableA.EmployeeName IS NULL OR TableB.EmployeeName IS NULL OR TableB.EmployeeName = TableA.EmployeeName)

我只从一个匹配的表中得到 NULL,我没有得到另一个表的扩展。在上面的示例中,我基本上只得到了第一行和第三行,而不是第二行。

有人可以帮我创建这个查询并告诉我这是如何正确完成的吗?

顺便说一句,我已经有一个看起来非常干净并填充内存表的存储过程,但这不是我想要的。

谢谢。

-- 编辑:

以下是当前不起作用的完整示例(缺少“someone 2”和“someone 3”。

DECLARE @Company TABLE
(
    CompanyID int
)

INSERT INTO @Company (CompanyID) VALUES (10)
INSERT INTO @Company (CompanyID) VALUES (12)

DECLARE @TableA TABLE
(
    EmployeeId int,
    CompanyId int,
    EmployeeName varchar(30)
)

DECLARE @TableB TABLE
(
    EmployeeId int,
    CompanyId int,
    EmployeeName varchar(30)
)

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 1, 10, 'someone' )

--INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
--VALUES ( 2, 12, 'someone 2' )

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 3, 12, 'someone 3' )

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 3, 12, 'someone 4' )

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 1, 10, 'someone' )

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 2, 12, 'someone 2' )

--INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
--VALUES ( 3, 12, 'someone 3' )

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 3, 12, 'someone 4' )

SELECT Company.CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM @Company Company
FULL OUTER JOIN @TableA TableA ON Company.CompanyID = TableA.CompanyID
FULL OUTER JOIN @TableB TableB ON Company.CompanyID = TableB.CompanyID
WHERE
(
    TableA.EmployeeName IS NULL OR TableB.EmployeeName IS NULL OR 
    TableB.EmployeeName = TableA.EmployeeName
)
AND Company.CompanyID = 12

结果:

CompanyID   EmployeeName    EmployeeName
12          someone 4       someone 4

我想要什么:

CompanyID   EmployeeName    EmployeeName
12          NULL            someone 2
12          someone 3       NULL
12          someone 4       someone 4

【问题讨论】:

  • 您实际上不需要查询Company 表来满足您的输出要求。尝试在两个表之间进行完全外连接。
  • @ar:我需要选择 CompanyID 并对其进行过滤。
  • 对于给定的数据,正确的结果应该是什么?你能包括那个吗
  • 您不能按照 ar 的建议进行操作(只需对表 A 和表 B 进行完全外部联接)并仅在 TableA.CompanyID 而不是 Company.CompanyID 上选择和过滤吗?或者您还需要从公司表中选择其他字段吗?
  • 我实际使用的两张表的性能其实真的很慢。它是 SQLXML 节点值的子选择。在加入之前从两个表中进行选择时,我绝对必须对 ID(在示例中为 CompanyID)进行预过滤。我知道如何使用存储过程中的临时表执行此操作,但要使其在可重用查询或视图中工作,我想看看这是否可能在整个查询底部的外部过滤器包括的 SELECT 中对 ID 的唯一引用,在示例中是 WHERE Company.CompanyID = 12。

标签: sql join outer-join full-outer-join


【解决方案1】:

试试这个:

SELECT Company.CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM Company
LEFT OUTER JOIN TableA ON Company.CompanyID = TableA.CompanyID
LEFT OUTER JOIN TableB ON Company.CompanyID = TableB.CompanyID
WHERE (TableA.EmployeeName IS NULL OR TableB.EmployeeName IS NULL OR TableB.EmployeeName = TableA.EmployeeName)

EDIT在OP给出测试数据和预期结果集后

试试这个(问题中的表格和测试数据):

DECLARE @Company TABLE (CompanyID int)
DECLARE @TableA TABLE (EmployeeId int,CompanyId int,EmployeeName varchar(30))
DECLARE @TableB TABLE (EmployeeId int,CompanyId int,EmployeeName varchar(30))

set nocount on
INSERT INTO @Company (CompanyID) VALUES (10)
INSERT INTO @Company (CompanyID) VALUES (12)

--INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )--VALUES ( 2, 12, 'someone 2' )
INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )VALUES ( 1, 10, 'someone' )
INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )VALUES ( 3, 12, 'someone 3' )
INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )VALUES ( 3, 12, 'someone 4' )

--INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )--VALUES ( 3, 12, 'someone 3' )
INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )VALUES ( 1, 10, 'someone' )
INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )VALUES ( 2, 12, 'someone 2' )
INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )VALUES ( 3, 12, 'someone 4' )
set nocount off

SELECT coalesce(TableA.CompanyID,TableB.CompanyID) CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM @TableA TableA
FULL OUTER jOIN  @TableB TableB ON TableA.CompanyID = TableB.CompanyID AND TableB.EmployeeName = TableA.EmployeeName
WHERE coalesce(TableA.CompanyID,TableB.CompanyID) = 12

输出:

CompanyID   EmployeeName                   EmployeeName
----------- ------------------------------ ------------------------------
12          NULL                           someone 2
12          someone 3                      NULL
12          someone 4                      someone 4

(3 row(s) affected)

【讨论】:

  • 谢谢;不起作用,因为它会为每个表提取匹配项(没有扩展 ~ 根本没有 NULL)。
  • 什么是no expansion ~ no NULLs at all??只需编辑您的问题以列出所有三个表中的示例数据,以及该数据的输出结果集应该是什么。
  • 感谢您的修改。我以为它看起来像那样。
  • 我看到你再次编辑了你(我相信这是你的答案?)有 UNION JOIN 的地方。我正在考虑所有选项,性能至关重要,因为连接表非常慢,因此需要对每个表进行预过滤。
【解决方案2】:

FULL OUTER JOIN 应该只在 companyID 和 employeeName 上的 TableA 和 TableB 之间进行,因为如果它只存在于一个表上,您希望将其填充为 NULL。
获得此信息后,您可以与 Company 进行内部联接以从 Company 获取其他数据。

全外连接解决方​​案:

select Company.companyID, EmployeeNameA, EmployeeNameB
from (
    SELECT isnull(TableA.CompanyID, TableB.CompanyID) as companyID,
        TableA.EmployeeName as EmployeeNameA,
        TableB.EmployeeName as EmployeeNameB
    FROM @TableA TableA 
    FULL OUTER JOIN @TableB TableB ON TableA.EmployeeName = TableB.EmployeeName and TableA.companyID = TableB.companyID
    WHERE
     TableA.CompanyID = 12 or TableB.CompanyID = 12 
) merged
inner join @Company Company
    on merged.companyID = Company.companyID

就我个人而言,我发现很难从 FULL OUTER JOINS 的角度进行思考。我对此的方法是:通过在受影响的表之间创建一个 UNION,在结果中找到您需要的不同 EmployeeNames,然后使用左连接从两个表中获取数据,从而在您应该获得 NULL 时。

左连接示例:

select c.companyID, a.employeeName, b.employeeName
from  (
    select distinct employeeName, companyID
    from  (
        select a.employeeName, companyID 
        from @tableA  a
        union 
        select b.employeeName, companyID
        from @tableB b
    ) a
) z
inner join @company c
    on c.companyID = z.companyID
left join @tableA  a
    on z.companyID = a.companyID and z.employeeName = a.employeeName
left join @tableB  b
    on z.companyID = b.companyID and z.employeeName = b.employeeName
where z.companyID = 12

【讨论】:

  • 谢谢。性能至关重要,因为连接的表非常慢,需要在连接之前根据 CompanyID 进行预过滤。
  • .. 公平地说,您提供的 FULL OUTER 示例确实对正在连接的表进行了预过滤,这很棒。但是 12 的过滤器在嵌套查询中是硬编码的,无法传递到视图中。
  • 实际上,您的第一个示例中的嵌套查询可以在没有外包装的情况下独立存在。这给了我一些可以使用的东西。谢谢!!
  • 是的,我看到了您关于嵌套查询中 where 条件的观点。关于性能,一种方法(如果您当然可以更改表结构)是引入一个 Employee(employeeID,companyID,employeeName,companyID)表,然后更改 TableA 和 TableB 以使用employeeID 而不是employeeName。在这种情况下,您将使用 TableA 和 TableB 离开 join Employeee
【解决方案3】:

试试这个

DECLARE @Company TABLE 
( 
    CompanyID int 
) 

INSERT INTO @Company (CompanyID) VALUES (10) 
INSERT INTO @Company (CompanyID) VALUES (12) 

DECLARE @TableA TABLE 
( 
    EmployeeId int, 
    CompanyId int, 
    EmployeeName varchar(30) 
) 

DECLARE @TableB TABLE 
( 
    EmployeeId int, 
    CompanyId int, 
    EmployeeName varchar(30) 
) 

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 1, 10, 'someone' ) 

--INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
--VALUES ( 2, 12, 'someone 2' ) 

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 3, 12, 'someone 3' ) 

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 3, 12, 'someone 4' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 1, 10, 'someone' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 2, 12, 'someone 2' ) 

--INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
--VALUES ( 3, 12, 'someone 3' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 3, 12, 'someone 4' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )  
VALUES ( 3, 12, 'someone 4' )  

SELECT Company.CompanyID,  
   A.EmployeeNameTableA,  
   A.EmployeeNameTAbleB 
FROM @Company Company  
left OUTER JOIN (select TableA.EmployeeName as EmployeeNameTableA, TableB.EmployeeName as EmployeeNameTableB , 
coalesce(TableA.CompanyID,TableB.CompanyID) as CompanyID 
from @TableA TableA  
FULL OUTER JOIN @TableB TableB ON TableA.CompanyID = TableB.CompanyID and TableB.EmployeeName = TableA.EmployeeName and (tablea.companyid = 12 or tableb.companyid = 12))A ON Company.CompanyID = A.CompanyID  

WHERE Company.CompanyID = 12  

【讨论】:

  • 这不是一个存储过程,它是一个独立的查询。
  • 对不起,你是对的。我很着急,没有向下滚动。 :)
  • 是否可以在嵌套查询中预先过滤 CompanyID?我的两个表的性能真的很慢,所以如果它在过滤之前选择了所有东西,那将不起作用。
【解决方案4】:

这是 Dimitris Baltas 答案的变体,更接近我的想法。

SELECT Company.CompanyID,
    TableA.EmployeeName as EmployeeNameTableA,
    TableB.EmployeeName as EmployeeNameTableB
FROM @TableA TableA 
FULL OUTER JOIN @TableB TableB ON TableA.EmployeeName = TableB.EmployeeName 
    and TableA.companyID = TableB.companyID
INNER JOIN @Company Company ON (
    Company.CompanyID = TableA.CompanyId OR Company.CompanyID = TableB.CompanyId
)
WHERE Company.CompanyID = 12

我正在寻找的一个关键点(这个示例没有这样做,但 Dimitris 可能)是对 CompanyID 进行预过滤,以便执行计划不会筛选每个连接表的所有行在过滤掉 CompanyID 之前。就我而言,这两个连接表非常慢。

我认为我最终要做的就是继续使用存储过程。

【讨论】:

    猜你喜欢
    • 2013-06-02
    • 2013-04-16
    • 2012-03-15
    • 2017-07-26
    • 2015-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-06
    相关资源
    最近更新 更多