外部连接中的意外 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
重新添加到另一方的列中。
您的查询首先执行 A
和 C
的完全外连接,因此首先查看结果。
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 |
+----+----+------+----+
然后这完全外连接到B
。 B
的内容是
+------+------+
| 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
重新添加(这些行是C3
是1
或2
)
+----+------+------+----+------+----+
| 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
的不匹配行(这些是B2
是2
或4
的行)
给你最终结果
+------+------+------+------+------+------+
| 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的主要内容,如果未能解决你的问题,请参考以下文章