Нужно ли обрабатывать пустые значения в левом соединении? - PullRequest
0 голосов
/ 08 мая 2018

В офисе есть более старший разработчик SQL (DBA), который сказал мне, что во всех левых соединениях моего сценария я должен обрабатывать сценарий, в котором столбец соединения левой таблицы, возможно, равен нулю, в противном случае я должны использовать внутренние соединения. Теперь, будучи новичком, я могу ошибаться здесь, но я не вижу его смысла и оставил меня без нужды в замешательстве.

Его объяснение было таковым, если только столбец не обнуляем, либо я должен

  1. используйте ISNULL(LeftTable.ColumnA,<replacement value here>) в предложении ON или
  2. обрабатывать нулевые значения в предложении ON или Предложение WHERE, добавив AND LeftTable.ColumnA IS NOT NULL или AND LeftTable.ColumnA IS NULL.

Я думал, что в этом нет необходимости, поскольку каждый использует LEFT JOIN, если не возражает против возврата пустых строк из правой таблицы, если значения правого столбца объединения таблиц не совпадают с левым столбцом объединения таблиц, будь то использование равенство или неравенство. Мое намерение состоит в том, что это не должно быть равным правым значениям столбца соединения таблицы. Если левый столбец соединения таблицы имеет значение null, я могу вернуть пустые строки в правой таблице, поскольку значение null не равно ничему.

Что я здесь не вижу?

ОСНОВНОЕ РЕДАКТИРОВАНИЕ:

Итак, я добавляю определения таблиц и сценарии. Это не точные сценарии, просто чтобы проиллюстрировать проблему. Я удалил более ранние правки, которые были неправильными, так как ранее их не было в сценарии.

CREATE TABLE dbo.Contact (
    ContactID int NOT NULL, --PK
    FirstName varchar(10) NULL, 
    LastName varchar(10) NULL,
    StatusID int NULL,
    CONSTRAINT PK_Contact_ContactID 
        PRIMARY KEY CLUSTERED (ContactID)   
);
GO

CREATE TABLE dbo.UserGroup (
    UserGroupID int NOT NULL, --PK
    UserGroup varchar(50) NULL,    
    StatusID int NULL,
    CONSTRAINT PK_UserGroup_UserGroupID
        PRIMARY KEY CLUSTERED (UserGroupID)
);
GO

CREATE TABLE dbo.UserGroupContact (
    UserGroupID int NOT NULL, --PK,FK
    ContactID int NOT NULL,  --PK,FK
    StatusID int NULL
    CONSTRAINT PK_UserGroupContact_UserGroupContactID 
        PRIMARY KEY CLUSTERED (UserGroupID, ContactID),
    CONSTRAINT FK_UserGroupContact_UserGroupId
        FOREIGN KEY (UserGroupId) 
        REFERENCES [dbo].[UserGroup](UserGroupId),
    CONSTRAINT FK_UserGroupContact_ContactId 
        FOREIGN KEY (ContactId) 
        REFERENCES [dbo].[Contact](ContactId)
);
GO

CREATE TABLE dbo.Account (
    AccountID int NOT NULL,  --PK
    AccountName varchar(50) NULL,   
    AccountManagerID int NULL, --FK     
    Balance int NULL,
    CONSTRAINT PK_Account_AccountID 
        PRIMARY KEY CLUSTERED (AccountID),
    CONSTRAINT FK_Account_AccountManagerID 
        FOREIGN KEY (AccountManagerID) 
        REFERENCES [dbo].[Contact](ContactId),
);
GO

Мой оригинальный запрос будет выглядеть так, как показано ниже. Когда я говорю «левая таблица», я имею в виду таблицу слева от предложения ON в объединении. Если «правильная таблица», то это таблица справа от предложения ON.

SELECT 
    a.AccountId,
    a.AccountName,
    a.Balance,              
    ug.UserGroup,
    ugc.UserGroupID,                
    a.AccountManagerID,
    c.FirstName,
    c.LastName
FROM  dbo.Account a             
    LEFT JOIN dbo.Contact c 
        ON a.AccountManagerID = c.ContactID     
        AND c.StatusID=1                                    
    LEFT JOIN dbo.UserGroupContact ugc 
        ON a.AccountManagerID = ugc.ContactID   
            AND ugc.StatusID=1
    LEFT JOIN dbo.UserGroup ug 
        ON ugc.UserGroupID  = ug.UserGroupID    
            AND ug.StatusID=1                           
WHERE 
    a.Balance > 0   
    AND ugc.UserGroupID = 10   
    AND a.AccountManagerID NOT IN (20,30)

Обратите внимание, что в приведенном выше примере сценария первое и второе левые объединения имеют столбец, допускающий значение NULL, в левой таблице и столбец, не имеющий значения NULL, в правой таблице. Третье левое объединение имеет оба столбца, которые можно обнулять в левой и правой таблицах.

Было предложено «перейти к внутреннему соединению или обработать условие NULL в предложении where» или «Возможно использование LEFT JOIN, но в предложении WHERE есть ссылки на ненулевые условия».

В зависимости от намерения предлагается выполнить одно из следующих действий:

a) преобразовать во внутреннее объединение (невозможно, так как я хочу получить несопоставленные строки из таблицы Account)

SELECT 
    a.AccountId,
    a.AccountName,
    a.Balance,              
    ug.UserGroup,
    ugc.UserGroupID,                
    a.AccountManagerID,
    c.FirstName,
    c.LastName
FROM  dbo.Account a             
    INNER JOIN dbo.Contact c 
        ON a.AccountManagerID = c.ContactID     
        AND c.StatusID=1                                    
    INNER JOIN dbo.UserGroupContact ugc 
        ON a.AccountManagerID = ugc.ContactID   
            AND ugc.StatusID=1
    INNER JOIN dbo.UserGroup ug 
        ON ugc.UserGroupID  = ug.UserGroupID
            AND ug.StatusID=1                           
WHERE 
    a.Balance > 0   
    AND ugc.UserGroupID = 10   
    AND a.AccountManagerID NOT IN (20,30)

b) обрабатывать пустые значения в предложении WHERE (невозможно, так как я хочу вернуть строки с нулевыми значениями в столбце a.AccountManagerID и в ugc.UserGroupID)

SELECT 
    a.AccountId,
    a.AccountName,
    a.Balance,              
    ug.UserGroup,
    ugc.UserGroupID,                
    a.AccountManagerID,
    c.FirstName,
    c.LastName
FROM  dbo.Account a             
    LEFT JOIN dbo.Contact c 
        ON a.AccountManagerID = c.ContactID     
        AND c.StatusID=1                                    
    LEFT JOIN dbo.UserGroupContact ugc 
        ON a.AccountManagerID = ugc.ContactID   
            AND ugc.StatusID=1
    LEFT JOIN dbo.UserGroup ug 
        ON ugc.UserGroupID  = ug.UserGroupID
            AND ug.StatusID=1                           
WHERE 
    a.Balance > 0   
    AND ugc.UserGroupID = 10   
    AND a.AccountManagerID NOT IN (20,30)
    AND a.AccountManagerID IS NOT NULL
    AND ugc.UserGroupID IS NOT NULL

c) обрабатывать пустые значения в предложении ON (я остановился на этом, что, как мне показалось, не имеет смысла, потому что это избыточно)

SELECT 
    a.AccountId,
    a.AccountName,
    a.Balance,              
    ug.UserGroup,
    ugc.UserGroupID,                
    a.AccountManagerID,
    c.FirstName,
    c.LastName
FROM  dbo.Account a             
    LEFT JOIN dbo.Contact c 
        ON a.AccountManagerID = c.ContactID     
        AND c.StatusID=1                                    
        AND a.AccountManagerID IS NOT NULL
    LEFT JOIN dbo.UserGroupContact ugc 
        ON a.AccountManagerID = ugc.ContactID   
            AND ugc.StatusID=1
            AND a.AccountManagerID IS NOT NULL
    LEFT JOIN dbo.UserGroup ug 
        ON ugc.UserGroupID  = ug.UserGroupID
            AND ug.StatusID=1               
            AND ugc.UserGroupID IS NOT NULL         
WHERE 
    a.Balance > 0   
    AND ugc.UserGroupID = 10   
    AND a.AccountManagerID NOT IN (20,30)

Я не привел пример для ISNULL (). Кроме того, я думаю, что он не имел в виду неявные внутренние объединения.

Напомним, как мне справиться с этим предложением: «Существует LEFT JOIN, но есть ненулевые условия, на которые есть ссылка в предложении WHERE.»? Он прокомментировал, что это «сомнительная логика левого соединения».

Ответы [ 4 ]

0 голосов
/ 10 мая 2018

каждый использует LEFT JOIN, если не возражает против возврата пустых строк из правой таблицы

Левая таблица LEFT JOIN правая таблица ON условие возвращает строки INNER JOIN плюс несопоставленные строки левой таблицы, расширенные нулями.

Один использует левое соединение, если это то, что он хочет.

столбец соединения левой таблицы

Объединение не в "столбце соединения" - что бы это ни значило. На состояние .

То, что может , скажем, быть одним столбцом в левой таблице, равным столбцу с тем же именем в правой. Или быть функцией одного столбца в левой таблице, равной столбцу с тем же именем в правой. Или быть логической функцией одноименных столбцов. Или включите / включите любой из них. Или быть любой логической функцией любого из входных столбцов.

Если левый столбец объединения таблиц равен нулю, я могу вернуть пустые строки в правой таблице, поскольку нулевое значение не равно ничему.

Кажется, вы страдаете от фундаментального заблуждения. Единственное, что «нормально для меня - вернуть» - это строк, которые вам сказали вернуть, для определенного возможного ввода .

Это не вопрос, скажем, кодирования какого-либо условия в некоторых таблицах, потому что мы хотим получить определенные строки внутреннего соединения, а затем принимать любые строки с нулевым расширением, которые мы получаем. Если мы используем левое соединение, это потому, что оно возвращает правильные строки внутреннего соединения и правильные строки с нулевым расширением; в противном случае мы хотим другое выражение .

Дело не в том, что, скажем, левая строка таблицы имеет нулевое значение, что означает, что эта строка не должна быть частью внутреннего соединения и должна быть расширена нулем. У нас есть некоторый вклад; мы хотим некоторый вывод. Если нам нужно внутреннее объединение двух таблиц для некоторого условия , независимо от того, как это условие использует пустые значения или любые другие входные значения плюс несопоставленные строки левой таблицы, тогда мы оставляем объединение этих таблиц в этом условии; в противном случае мы хотим другое выражение .

(Ваш вопрос использует, но не объясняет «дескриптор». Вы не сообщаете нам строк, которые вам сказали возвращать для определенного возможного ввода . Вы даже не даете нам желаемый пример вывод, например, ввод или ваш фактический вывод для какого-либо запроса. Таким образом, у нас нет возможности переадресовать то, что пытается сказать критика вашего администратора баз данных о том, что вы должны делать или что вы делаете в своих запросах.)

0 голосов
/ 08 мая 2018

в моих глазах это очень просто, насколько я понял.

Давайте попробуем с примером. Представьте, что у вас есть 2 таблицы, мастер и таблица с подробностями.

МАСТЕР СТОЛ "TheMaster"

ID  NAME
1   Foo1
2   Foo2
3   Foo3
4   Foo4
5   Foo5
6   Foo6

ДЕТАЛИ СТОЛ "TheDetails"

ID  ID_FK   TheDetailValue
1   1   3
2   1   5
3   3   3
4   5   2
5   5   9
6   3   6
7   1   4

Таблица TheDetails связана с таблицей TheMaster через поле ID_FK. Теперь представьте себе, что нужно выполнить запрос, в котором необходимо суммировать значения столбца TheDetailValue. Я бы пошел с чем-то вроде этого:

SELECT TheMaster.ID, TheMaster.NAME, Sum(TheDetails.TheDetailValue) AS SumOfTheDetailValue
FROM TheMaster INNER JOIN TheDetails ON TheMaster.ID = TheDetails.ID_FK
GROUP BY TheMaster.ID, TheMaster.NAME;

Вы получите список, подобный этому:

ID  NAME    SumOfTheDetailValue
1   Foo1    12
3   Foo3    9
5   Foo5    11

Но какой ваш запрос использует левое соединение вместо внутреннего соединения? Например:

SELECT TheMaster.ID, TheMaster.NAME, Sum(TheDetails.TheDetailValue) AS SumOfTheDetailValue
FROM TheMaster LEFT JOIN TheDetails ON TheMaster.ID = TheDetails.ID_FK
GROUP BY TheMaster.ID, TheMaster.NAME;

Результат будет:

ID  NAME    SumOfTheDetailValue
1   Foo1    12
2   Foo2    
3   Foo3    9
4   Foo4    
5   Foo5    11
6   Foo6    

Вы получите NULL для каждого основного поля, не имеющего значений в таблице сведений. Как вы исключаете эти значения? Используя ISNULL!

SELECT TheMaster.ID, TheMaster.NAME, Sum(TheDetails.TheDetailValue) AS SumOfTheDetailValue
FROM TheMaster LEFT JOIN TheDetails ON TheMaster.ID = TheDetails.ID_FK
WHERE (((TheDetails.ID_FK) Is Not Null))
GROUP BY TheMaster.ID, TheMaster.NAME;

... что привело бы нас к следующим результатам:

ID  NAME    SumOfTheDetailValue
1   Foo1    12
3   Foo3    9
5   Foo5    11

... это именно то, что мы получили до использования INNER JOIN.

Итак, в конце концов, я полагаю, ваш коллега говорит об использовании функции ISNULL, чтобы исключить записи, не имеющие отношения к другой таблице.

Вот и все.

Например, с помощью MS Access (экспресс-тест) был сделан только запрос, поэтому функция ISNULL реализована с помощью функции "Is Null", которая может стать "Is Not Null". В вашем случае, вероятно, это что-то вроде ISNULL () и / или NOT ISNULL ()

0 голосов
/ 09 мая 2018

Единственное, о чем ваш вопрос не говорит, это NULL ANSI, независимо от того, включены они или нет. Если ANSI NULL включены, сравнение NULL = NULL возвращает false, но если они выключены, NULL = NULL возвращает true.

Подробнее об NULL ANSI можно прочитать здесь: https://docs.microsoft.com/en-us/sql/t-sql/statements/set-ansi-nulls-transact-sql

Таким образом, если значения ANSI NULL выключены, вам очень важно сопоставить внешний ключ NULL с отсутствующей строкой в ​​соединении. Ваши строки с внешними ключами NULL будут совпадать с каждой строкой, где в левой таблице были все NULL.

Если ANSI NULL включены, LEFT OUTER JOIN будет работать должным образом, а внешние ключи NULL не будут совпадать с первичными ключами NULL других отсутствующих строк.

Если другой разработчик говорит вам, что вы должны быть осторожны с NULL в OUTER JOIN, это, вероятно, является хорошим признаком того, что в базе данных, с которой вы работаете, отключены ANSI NULL.

0 голосов
/ 08 мая 2018

Собираюсь немного расширить мой комментарий здесь; это, однако, работа по догадкам, основанная на том, что мы имеем на данный момент.

Исходя из вашей текущей формулировки, то, что вы указали, неверно. Давайте возьмем эти простые таблицы:

USE Sandbox;
GO

CREATE TABLE Example1 (ID int NOT NULL, SomeValue varchar(10));
GO
CREATE TABLE Example2 (ID int NOT NULL, ParentID int NOT NULL, SomeOtherValue varchar(10));
GO

INSERT INTO Example1
VALUES (1,'abc'),(2,'def'),(3,'bcd'),(4,'zxy');
GO
INSERT INTO Example2
VALUES (1,1,'sadfh'),(2,1,'asdgfkhji'),(3,3,'sdfhdfsbh');

Теперь давайте выполним простой запрос с LEFT JOIN:

SELECT *
FROM Example1 E1
     LEFT JOIN Example2 E2 ON E1.ID = E2.ParentID
ORDER BY E1.ID, E2.ID;

Обратите внимание, что возвращается 5 строк. Не требуется обработка NULL. если вы добавите OR к ON, это будет бессмысленно, поскольку ParentID не может иметь значение NULL.

Если, однако, мы добавим что-то к WHERE, например:

SELECT *
FROM Example1 E1
     LEFT JOIN Example2 E2 ON E1.ID = E2.ParentID
WHERE LEFT(E2.SomeOtherValue,1) = 's'
ORDER BY E1.ID, E2.ID;

Это теперь превращает LEFT JOIN в неявное INNER JOIN. Следовательно, вышеприведенное было бы лучше записать как:

SELECT *
FROM Example1 E1
     JOIN Example2 E2 ON E1.ID = E2.ParentID
WHERE LEFT(E2.SomeOtherValue,1) = 's'
ORDER BY E1.ID, E2.ID;

Это, однако, может не быть намеченным результатом; вам вполне могут потребоваться несопоставленные строки (и почему вы изначально использовали LEFT JOIN. Есть два способа сделать это. Первый - добавить критерии к предложению ON:

SELECT *
FROM Example1 E1
     LEFT JOIN Example2 E2 ON E1.ID = E2.ParentID
                          AND LEFT(E2.SomeOtherValue,1) = 's'
ORDER BY E1.ID, E2.ID;

Другой вариант - добавить OR (не используйте ISNULL, это влияет на SARGability!):

SELECT *
FROM Example1 E1
     LEFT JOIN Example2 E2 ON E1.ID = E2.ParentID
WHERE LEFT(E2.SomeOtherValue,1) = 's'
   OR E2.ID IS NULL
ORDER BY E1.ID, E2.ID;

Я полагаю, это то, о чем говорит ваш старший.

Повторить, хотя:

SELECT *
FROM Example1 E1
     LEFT JOIN Example2 E2 ON E1.ID = E2.ParentID OR E2.ID IS NULL
ORDER BY E1.ID, E2.ID;

Не имеет смысла. E2.ID не может иметь значение NULL, поэтому предложение не вносит изменений в запрос, за исключением, вероятно, замедления его выполнения.

Cleanup:

DROP TABLE Example1;
DROP TABLE Example2;
...