我是否需要在LEFT JOIN上处理空值?

时间:2018-05-08 08:53:11

标签: sql-server null left-join

办公室里有一位更高级的SQL开发人员(DBA)告诉我,在我脚本的所有LEFT JOINS中,我必须处理左表的join列可能为null的情况,否则,我必须使用INNER JOIN。现在,作为一个菜鸟,我可能在这里错了,但我看不出他的观点,让我不必要地迷茫。

他的解释是,除非该列不可为空,否则我必须

  1. 在ON子句上使用ISNULL(LeftTable.ColumnA,<replacement value here>),或
  2. 处理ON子句中的空值或 WHERE子句,可以通过添加AND LeftTable.ColumnA IS NOT NULLAND LeftTable.ColumnA IS NULL
  3. 我认为这些是不必要的,因为如果一个人不介意从右表中返回空行,如果右表连接列的值与左表连接列不匹配,则使用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
    

    我的原始查询如下所示。当我说'#34; left table&#34;时,我指的是连接中ON子句左边的表。如果&#34;右表&#34;,则表示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)
    

    注意在上面的示例脚本中,第一个和第二个左连接在左表上有一个可为空的列,在右表上有一个不可为空的列。第三个左连接在左表和右表上都有可为空的列。

    建议是&#34;更改为内连接或处理where子句&#34;中的NULL条件。或&#34;使用LEFT JOIN,但WHERE子句中引用了非空条件。&#34;

    建议根据意图做其中任何一项:

    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()的示例。另外,我认为他并不是指隐含的内部联接。

    回顾一下,我该如何处理这个建议:&#34;使用LEFT JOIN但是在WHERE子句中引用了非空条件。&#34;?他评论说这是一个有问题的LEFT JOIN逻辑&#34;。

4 个答案:

答案 0 :(得分:1)

  

如果不介意从右表中返回空行

,则使用LEFT JOIN

左表 LEFT JOIN 右表 ON 条件返回INNER JOIN行以及由空值扩展的不匹配的左表行。

一个人使用左连接,如果这是你想要的。

  

左表的连接列

连接不在“连接列”上 - 无论这意味着什么。它位于条件

可能,例如,左表中的一列等于右边的同名列。或者是左表中一列的函数等于右边的同名列。或者是同名列的布尔函数。或涉及/包括任何这些。 或者是任何输入列的任何布尔函数。

  

如果左表连接列为null,则可以在右表上返回空行,因为null不等于任何内容。

看来你正遭受一种根本的误解。对我来说,唯一可以“返回”的是您被告知返回的行,以获取某些可能的输入

在某些表上编写某些条件并不是问题,因为我们需要某些内连接行,然后接受我们得到的任何空扩展行。如果我们使用左连接,那是因为它返回正确的内连接行&amp;正确的空扩展行;否则我们想要一个不同的表达

这不是一个问题,例如,左表行具有null意味着该行不能是内部连接的一部分&amp;必须为空扩展。我们有一些意见;我们想要一些输出。如果我们想要某些条件上的两个表的内连接,无论该条件如何使用空值或任何其他输入值加上不匹配的左表行,那么我们就在这个条件下连接那些表;否则我们想要一个不同的表达

(你的问题使用但不解释“句柄”。你没有告诉我们你被告知要返回的行,对于某些可能的输入。你甚至不给我们举例例如输入所需的输出或某些查询的实际输出。所以我们无法添加你的DBA批评试图说明你应该做什么或你正在做什么查询的内容。)

答案 1 :(得分:0)

在这里扩展我的评论;然而,这是根据我们目前的情况猜测工作。

根据您目前的措辞,您说的是错误的。我们来看看这些简单的表格:

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 ,因此该条款不会对查询进行任何更改,除非可能使其运行速度变慢。

清理:

DROP TABLE Example1;
DROP TABLE Example2;

答案 2 :(得分:0)

据我所知,在我看来,这很简单。

让我们试试一个例子。 想象一下,有2个表,一个主表和一个详细信息表。

MASTER TABLE“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表通过字段ID_FK链接到TheMaster表。 现在,假设运行一个查询,您需要对列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

但是,你的查询是使用LEFT JOIN而不是INNER JOIN?例如:

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()

答案 3 :(得分:0)

One thing your question doesn't talk about is ANSI NULLs, whether they're on or off. If ANSI NULLs are on, comparing NULL = NULL return false, but if they're off, NULL = NULL returns true.

You can read more about ANSI NULLs here: https://docs.microsoft.com/en-us/sql/t-sql/statements/set-ansi-nulls-transact-sql

So if ANSI NULLs are OFF, you very much care about matching a NULL foreign key to missing row in a join. Your rows with NULL foreign keys are going to match every single row where the left table was all NULLs.

If ANSI NULLs are ON, the LEFT OUTER JOIN will behave as expected, and NULL foreign keys will not match up with NULL primary keys of other missing rows.

If another dev is telling you that you need to be careful about NULLs in OUTER JOINs, that's probably a good indication that the database you're working with has ANSI NULLs OFF.