Неожиданные NULL во внешнем соединении - PullRequest
3 голосов
/ 30 апреля 2019

У меня есть 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 оба имеют внешние ключи для TableA через Key1. На практике TableC также может ссылаться на TableB через Key1 и Key2 вместе, если 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 отсутствует?

EDIT:

Вот последний исправленный запрос после того, как я узнал, что я делал неправильно:

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

Ответы [ 3 ]

1 голос
/ 30 апреля 2019

Почему 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 |
+------+------+

INNER JOIN из этих двух наборов результатов с предикатом ON (TableB.Key1 = [A1] AND [C1] IS NULL) OR ([C1] = TableB.Key1 AND [C2] = TableB.Key2) возвращает только 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 |
+----+------+------+----+------+----+

и несопоставленные строки из B согласно RIGHT JOIN (это были те, где 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 |
+------+------+------+------+------+------+
0 голосов
/ 30 апреля 2019
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 и использовал соединение, чтобы собрать их вместе.

Надеюсь, это поможет вам ...

0 голосов
/ 30 апреля 2019

Вот то, что вы хотите - обратите внимание ... вам нужен полный ауттер на 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...