LEFT OUTER JOIN 未按预期运行

Posted

技术标签:

【中文标题】LEFT OUTER JOIN 未按预期运行【英文标题】:LEFT OUTER JOINs not acting as expected 【发布时间】:2014-02-20 21:58:50 【问题描述】:

查询 #1

SELECT     
     dbo.CLIENT.CLIENT_ID, dbo.CLIENT.GOC, dbo.SALES_UW_REGION.SALES_UNDERWRITING
FROM         dbo.CLIENT LEFT OUTER JOIN
                      dbo.SALES_UW_REGION ON dbo.CLIENT.GOC = dbo.SALES_UW_REGION.GOC
WHERE     (dbo.CLIENT.CLIENT_ID = 23721)

CLIENT_ID, GOC, SALES_UNDERWRITING
23721   332 Underwriting
23721   332 Sales

我可以理解为什么这只会返回一行,原因是尽管 LEFT 外连接确保返回两个 CLIENT 记录,即使它们不匹配,但在连接之后应用了 FILTER,因此结果集只有一个行。

查询 #2

SELECT     
     dbo.CLIENT.CLIENT_ID, dbo.CLIENT.GOC, dbo.SALES_UW_REGION.SALES_UNDERWRITING
FROM         dbo.CLIENT LEFT OUTER JOIN
                      dbo.SALES_UW_REGION ON dbo.CLIENT.GOC = dbo.SALES_UW_REGION.GOC                      
WHERE     (dbo.CLIENT.CLIENT_ID = 23721)
and SALES_UW_REGION.SALES_UNDERWRITING = 'Sales '

CLIENT_ID   GOC SALES_UNDERWRITING
23721   332 Sales

但是,如果我将“SALES”过滤器移动到 JOIN 子句,我惊讶地发现仍然返回一行。

查询 #3

SELECT     
     dbo.CLIENT.CLIENT_ID, dbo.CLIENT.GOC, dbo.SALES_UW_REGION.SALES_UNDERWRITING
FROM         dbo.CLIENT LEFT OUTER JOIN
                      dbo.SALES_UW_REGION ON dbo.CLIENT.GOC = dbo.SALES_UW_REGION.GOC                      
                      and SALES_UW_REGION.SALES_UNDERWRITING = 'Sales '
WHERE     (dbo.CLIENT.CLIENT_ID = 23721)

CLIENT_ID   GOC SALES_UNDERWRITING
23721   332 Sales

我预计,因为它是 JOIN 子句的一部分,并且连接是 LEFT OUTER,所以我会得到 2 行。一般来说,如果 JOIN 条件涉及两列的相等匹配,一列来自 LEFT 表,另一列来自 RIGHT 表,则在 LEFT OUTER JOIN 中,LEFT 表将返回 NULL 值,用于从对。如果我们将 RIGHT 表中的值与文字匹配,为什么会有任何不同?不应该返回 LEFT 表中的行吗?

伙计,我认为这是我已经掌握的基本内容......

这是你需要重新创建我所做的事情:

CREATE TABLE [dbo].[CLIENT](
    [CLIENT_ID] [bigint] NOT NULL,
    [GOC] [char](3) NULL
) 
go

CREATE TABLE [dbo].[SALES_UW_REGION](
    [GOC] [char](3) NOT NULL,
    [SALES_UNDERWRITING] [varchar](12) NULL
) 
go


INSERT INTO [dbo].[CLIENT]([CLIENT_ID], [GOC])
SELECT 23721, N'332'
go

INSERT INTO [dbo].[SALES_UW_REGION]([GOC], [SALES_UNDERWRITING])
SELECT N'332', N'Underwriting' UNION ALL
SELECT N'332', N'Sales'
go

【问题讨论】:

LEFT OUTER JOINS 永远不会按预期运行 :) 我相信左表(客户端)只有一条记录,它被重复的次数与右侧的匹配行一样多。当您过滤右表时,无论在哪里,笛卡尔积中的行数都会减少。唯一的例外是当右侧没有行时,您仍然会得到左行 - 这就是为什么存在左连接。 你一定是对的,但我觉得奇怪的是,如果我在 LEFT.COL1 = RIGHT.COL2 上执行 LEFT OUTER JOIN,如果 RIGHT 上没有匹配的记录,则 LEFT 行仍然是返回,但如果我替换 LEFT 上的文字,例如 'X' = RIGHT.COL2,那么 RIGHT 上匹配行的存在可能会影响 LEFT 行是否返回。好像不一致 【参考方案1】:

我认为您的困惑涉及对第一个结果集的误解:

CLIENT_ID、GOC、SALES_UNDERWRITING
23721 332 承保
23721 332 销售

虽然此结果集中有两行,但结果仅代表 Client 表中的一行数据。连接条件允许 Client 表中的单行匹配 Sales_UW_Region 表中的两行,因此 Client 表中该行的数据在结果集中重复。一开始这里只有一个Client记录,证明只有一个Client_ID,但是该记录的数据显示了两次:Sales_UW_Region中的每个匹配记录一次。

稍后,当您在连接的ON 子句中包含and SALES_UW_REGION.SALES_UNDERWRITING = 'Sales ' 条件时,Client 表中的原始单条记录仅匹配Sales_UW_Region 表中的一条记录。该行的数据不再需要复制,因此只返回一行。

【讨论】:

啊!当我将 SALES_UW_REGION_Table 中的两个记录都更改为“Underwriting”并执行仅针对 SALES 记录的查询 3 时,我得到 1 行返回给客户端,其中 SALES_UNDERWRITING 值为 NULL。这表明 对于 LEFT 表中的给定记录,LEFT OUTER JOIN 将确保 LEFT 行在整个结果集中至少出现一次,LEFT OUTER JOIN 并不意味着对于每个潜在的连接在匹配 CLIENT 值时,对于在 CLIENTID 上匹配但在连接过滤器中的其他条件上匹配失败的行,一行将出现在结果集中。【参考方案2】:

您的CLIENT 表中有一行CLIENT_ID = 23721

您将加入另一个具有多行 CLIENT_ID = 23721 的表(当在您的第一个查询中加入条件时),但是当连接条件发生更改时,它会从 RIGHT 表中排除其中一个行。

您将从LEFT 表中获取所有记录,无论它们是否加入RIGHT 表中的记录,正如预期的那样。

【讨论】:

是的,这听起来也不错,但我发现 Joel 的回答更容易理解。投票赞成,谢谢【参考方案3】:

你的理解是正确的。如果过滤器在 JOIN 的 ON 子句中,它不会从第一个表中删除行。

我认为您需要在其他地方寻找您的问题...您真的是要在 'Sales ' 常量中添加一个尾随空格吗?

这是一个 JSFiddle 来测试这个:http://sqlfiddle.com/#!2/bfe32/3/0

【讨论】:

我喜欢 Fiddler 的例子,但是当我点击链接时它对我不起作用,它都是空白的。谢谢。 关于尾随空格,(调整后的)SQL 是由 BI 报告生成的,我不会像那样手动编写它,但数据库设置忽略 char 和 varchar 字段中的尾随空格。不确定这是默认设置还是我处理的每个 SQL 数据库选项都选择了此设置。

以上是关于LEFT OUTER JOIN 未按预期运行的主要内容,如果未能解决你的问题,请参考以下文章

mysql where not in to left outer join

将非 FK 条件添加到 Django ORM 中的 LEFT OUTER JOIN 以返回不连接的行

关于mysql中的left join和left outer join的区别

MySQL 数据库中 left outer join 和 left join 啥区别

MySQL 数据库中 left outer join 和 left join 啥区别?

SQL中的left outer join,inner join,right outer join用法详解