【问题标题】:Unexpected NULLs in outer join外部连接中的意外 NULL
【发布时间】:2019-04-30 16:22:02
【问题描述】:

我要加入 3 张桌子。我们称它们为 TableA、TableB 和 TableC:

DECLARE @TableA TABLE
(
    Key1 int,
    PRIMARY KEY
    (
        Key1
    )
)
DECLARE @TableB TABLE
(
    Key1 int,
    Key2 int,
    PRIMARY KEY
    (
        Key1,
        Key2
    )
)
DECLARE @TableC TABLE
(
    Key3 int NOT NULL,
    Key1 int NOT NULL,
    Key2 int NULL,
    PRIMARY KEY
    (
        Key3
    )
)

以下是一些示例数据:

INSERT INTO @TableA (Key1) VALUES (1);
INSERT INTO @TableB (Key1, Key2) VALUES (1, 1), (1, 2), (1, 3), (1, 4)
INSERT INTO @TableC (Key3, Key1, Key2) VALUES (1, 1, NULL), (2, 1, NULL), (3, 1, 1), (4, 1, 3)

TableB 和 TableC 都有通过 Key1 指向 TableA 的外键。在实践中,TableC 也可以通过 Key1 和 Key2 组合来引用 TableB,如果 Key2 不为空,但没有实际的外键。 Key3 是无关紧要的,除了 Key1 和 Key2 不是 TableC 的主键的一部分。

我正在尝试编写一个结合了 TableB 和 TableC 的查询:

SELECT
    TableA.Key1 AS [A1],
    TableB.Key1 AS [B1],
    TableB.Key2 AS [B2],
    TableC.Key1 AS [C1],
    TableC.Key2 AS [C2],
    TableC.Key3 AS [C3]
FROM @TableA AS TableA
FULL OUTER JOIN @TableC AS TableC
    ON TableC.Key1 = TableA.Key1
FULL OUTER JOIN @TableB AS TableB
    ON (TableB.Key1 = TableA.Key1 AND TableC.Key1 IS NULL)
    OR (TableC.Key1 = TableB.Key1 AND TableC.Key2 = TableB.Key2)
WHERE (TableA.Key1 = TableB.Key1 OR TableA.Key1 = TableC.Key1)
ORDER BY TableB.Key2, TableC.Key2

我的期望是 TableB 和 TableC 都应该包含它们的所有行,匹配两个键上匹配的行,以及它们不匹配的 NULLS。

我希望得到这个:

A1       B1       B2       C1       C2      C3
1        NULL     NULL     1        NULL    1
1        NULL     NULL     1        NULL    2
1        1        1        1        1       3
1        1        2        NULL     NULL    NULL -- THIS ROW IS MISSING
1        1        3        1        3       4
1        1        4        NULL     NULL    NULL -- THIS ROW IS MISSING

但是我得到了这个:

A1       B1       B2       C1       C2      C3
1        NULL     NULL     1        NULL    1
1        NULL     NULL     1        NULL    2
1        1        1        1        1       3
1        1        3        1        3       4

如果我注释掉 WHERE 子句,我会得到我期望的所有行,除了 A1 对于缺失的行是 NULL:

A1       B1       B2       C1       C2      C3
1        NULL     NULL     1        NULL    1
1        NULL     NULL     1        NULL    2
1        1        1        1        1       3
NULL     1        2        NULL     NULL    NULL   -- A1 should be 1
1        1        3        1        3       4
NULL     1        4        NULL     NULL    NULL   -- A1 should be 1

为什么 TableA.Key1 返回 NULL 并导致它排除缺少 TableB.Key2 的行?

编辑:

这是我知道自己做错了什么后的最终固定查询:

SELECT
    TableA.Key1 AS A1,
    Subquery.*
FROM @TableA AS TableA
INNER JOIN
(
    SELECT
        TableB.Key1 AS [B1],
        TableB.Key2 AS [B2],
        TableC.Key1 AS [C1],
        TableC.Key2 AS [C2],
        TableC.Key3 AS [C3]
    FROM @TableC AS TableC
    FULL OUTER JOIN @TableB AS TableB
        ON TableB.Key1 = TableC.Key1 AND TableB.Key2 = TableC.Key2
) AS Subquery 
    ON Subquery.B1 = TableA.Key1 OR Subquery.C1 = TableA.Key1
ORDER BY Subquery.B2, Subquery.C2

【问题讨论】:

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


    【解决方案1】:

    为什么 TableA.Key1 返回 NULL 并导致它排除行 哪里缺少 TableB.Key2?

    完全外连接与INNER JOIN 相同,但任何一方的任何不匹配行都将用NULL 重新添加到另一方的列中。

    您的查询首先执行 AC 的完全外连接,因此首先查看结果。

    SELECT
        TableA.Key1 AS [A1],
        TableC.Key1 AS [C1],
        TableC.Key2 AS [C2],
        TableC.Key3 AS [C3]
    FROM @TableA AS TableA
    FULL OUTER JOIN @TableC AS TableC
        ON TableC.Key1 = TableA.Key1
    

    这将返回以下虚拟表 (VT1) 进入下一阶段。由于这与INNER JOIN 的结果相同,我怀疑它需要任何解释。 @TableC 中的每一行都成功匹配@TableA 中的单行。

    +----+----+------+----+
    | A1 | C1 |  C2  | C3 |
    +----+----+------+----+
    |  1 |  1 | NULL |  1 |
    |  1 |  1 | NULL |  2 |
    |  1 |  1 | 1    |  3 |
    |  1 |  1 | 3    |  4 |
    +----+----+------+----+
    

    然后这完全外连接到BB的内容是

    +------+------+
    | Key1 | Key2 |
    +------+------+
    |    1 |    1 |
    |    1 |    2 |
    |    1 |    3 |
    |    1 |    4 |
    +------+------+
    

    带有谓词ON (TableB.Key1 = [A1] AND [C1] IS NULL) OR ([C1] = TableB.Key1 AND [C2] = TableB.Key2) 的这两个结果集的INNER JOIN 仅返回2 行。

    +----+----+----+----+----+----+
    | A1 | B1 | B2 | C1 | C2 | C3 |
    +----+----+----+----+----+----+
    |  1 |  1 |  1 |  1 |  1 |  3 |
    |  1 |  1 |  3 |  1 |  3 |  4 |
    +----+----+----+----+----+----+
    

    VT1 中不匹配的行会按照LEFT JOIN 重新添加(这些行是C312

    +----+------+------+----+------+----+
    | A1 |  B1  |  B2  | C1 |  C2  | C3 |
    +----+------+------+----+------+----+
    |  1 | NULL | NULL |  1 | NULL |  1 |
    |  1 | NULL | NULL |  1 | NULL |  2 |
    |  1 | 1    | 1    |  1 | 1    |  3 |
    |  1 | 1    | 3    |  1 | 3    |  4 |
    +----+------+------+----+------+----+
    

    以及RIGHT JOIN 中来自B 的不匹配行(这些是B224 的行)

    给你最终结果

    +------+------+------+------+------+------+
    |  A1  |  B1  |  B2  |  C1  |  C2  |  C3  |
    +------+------+------+------+------+------+
    | 1    | NULL | NULL | 1    | NULL | 1    |
    | 1    | NULL | NULL | 1    | NULL | 2    |
    | 1    | 1    | 1    | 1    | 1    | 3    |
    | 1    | 1    | 3    | 1    | 3    | 4    |
    | NULL | 1    | 2    | NULL | NULL | NULL |
    | NULL | 1    | 4    | NULL | NULL | NULL |
    +------+------+------+------+------+------+
    

    【讨论】:

    • 你解释了为什么它会做它做的事情,但你没有说如何得到他想要的。
    • @Hogan - 我在问题中只看到一个问题。这是Why is TableA.Key1 coming back NULL and causing it to exclude rows where TableB.Key2 is missing? - 如果 OP 理解为什么他们当前的加入不能按照他们的意愿工作,他们可以应用这些知识来修复它。授之以鱼不如授之以渔
    • 谢谢,由于您的帮助,我知道我的查询出了什么问题。总而言之:在这种情况下,与 TableA 连接的 TableC 显然永远不会为空,因此针对这两者的组合的 OUTER JOIN 将强制整个左侧对于 TableB 中的不匹配行为 NULL。
    • @MartinSmith -- 好的点 -- 我读为 -- 让我的查询返回我期望的结果,而不是这些。
    【解决方案2】:

    这就是你想要的——注意……你想要一个完整的 B 和 C 的外部,所以 A 无关紧要——在你的示例的查询中甚至不需要它,但是你可以离开或内部随心所欲加入(我使用左加入)

    SELECT
      TableA.Key1 AS [A1],  -- Probably not needed
      TableB.Key1 AS [B1],
      TableB.Key2 AS [B2],
      TableC.Key1 AS [C1],
      TableC.Key2 AS [C2],
      TableC.Key3 AS [C3]
    FROM @TableB AS TableB
    FULL OUTER JOIN @TableC AS TableC ON TableB.Key1 = TableC.Key1 and TableB.Key2 = TableC.Key2
    LEFT JOIN @TableA AS TableA ON TableB.Key1 = TableA.Key1 -- Probably not needed
    

    【讨论】:

    • TableA 很重要,因为在实际情况下,TableA 中的其他列也返回 null。我想我应该编辑问题以反映这一点。
    • @BryceWagner——我想——这就是我说“可能”的原因。所以这对你有用。
    【解决方案3】:
    SELECT 
    a.Key1 AS [A1],
    b.Key1 AS [B1],
    b.Key2 AS [B2],
    c.Key1 AS [C1],
    c.Key2 AS [C2],
    c.Key3 AS [C3]
    FROM @TableB b
    LEFT JOIN @TableC c
        ON c.Key2 = b.Key2
    INNER JOIN @TableA a
        ON b.Key1 = a.Key1
    
    UNION
    
    SELECT 
        a.Key1 AS [A1],
        b.Key1 AS [B1],
        b.Key2 AS [B2],
        c.Key1 AS [C1],
        c.Key2 AS [C2],
        c.Key3 AS [C3]
    FROM @TableC c
    LEFT JOIN @TableB b
        ON c.Key2 = b.Key2
    INNER JOIN @TableA a
        ON c.Key1 = a.Key1
    

    输出:

    A1  B1  B2  C1  C2  C3
    1   NULL    NULL    1   NULL    1
    1   NULL    NULL    1   NULL    2
    1   1   1   1   1   3
    1   1   2   NULL    NULL    NULL
    1   1   3   1   3   4
    1   1   4   NULL    NULL    NULL
    

    我先拿到B端,然后拿到C端,用union把它们拉到一起。

    希望这对您有所帮助...

    【讨论】:

    • 是的,我考虑过使用 UNION,但考虑到实际查询的复杂性,这将是一个巨大的混乱。不是不可能,但不是我想要维护的东西。 Martin Smith 的回答实际上帮助我了解了真正发生的事情,因此我可以做对。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-22
    • 2017-03-01
    • 1970-01-01
    • 2020-12-23
    • 2015-12-24
    • 1970-01-01
    • 2021-01-31
    相关资源
    最近更新 更多