外部连接中的意外 NULL

Posted

技术标签:

【中文标题】外部连接中的意外 NULL【英文标题】:Unexpected NULLs in outer join 【发布时间】: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

【问题讨论】:

【参考方案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 的回答实际上帮助我了解了真正发生的事情,因此我可以做对。

以上是关于外部连接中的意外 NULL的主要内容,如果未能解决你的问题,请参考以下文章

外部应用在不匹配时意外返回列NOT NULL

啥是左外部联结?

“JSON 中的意外令牌”(通过 Zapier 连接的 Firestore)

来自外部设备的意外输入 - iOS

SSIS 基础连接已关闭:发送时发生意外错误

arcgis10.2挂接表格出现连接到数据库失败 常规功能故障 外部数据库驱动程序意外错误?