如何在MySQL中进行全面的连接?

Spe*_*cer 614 mysql sql join outer-join full-outer-join

我想在MySQL中进行全外连接.这可能吗?MySQL是否支持Full Outer Join?

Pab*_*ruz 618

您没有在MySQL上完全加入,但您可以确保模仿它们.

对于从此SO问题转录的代码SAMPLE,您有:

有两个表t1,t2:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Run Code Online (Sandbox Code Playgroud)

上面的查询适用于FULL OUTER JOIN操作不会产生任何重复行的特殊情况.上面的查询依赖于UNIONset运算符来删除查询模式引入的重复行.我们可以通过对第二个查询使用反连接模式来避免引入重复行,然后使用UNION ALL集合运算符来组合这两个集合.在更一般的情况下,FULL OUTER JOIN将返回重复的行,我们可以这样做:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION ALL
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.id IS NULL
Run Code Online (Sandbox Code Playgroud)

  • 这是正确的例子:`(SELECT ... FROM tbl1 LEFT JOIN tbl2 ...)UNION ALL(SELECT ... FROM tbl1 RIGHT JOIN tbl2 ... WHERE tbl1.col IS NULL) (154认同)
  • 实际上你写的东西不正确.因为当您执行UNION时,您将删除重复项,有时当您加入两个不同的表时,应该有重复项. (29认同)
  • @ypercube:如果`t1`和`t2`中没有重复的行,则此答案中的查询会返回一个模拟FULL OUTER JOIN的结果集.但是在更一般的情况下,例如,SELECT列表不包含足够的列/表达式以使返回的行唯一,那么此查询模式*不足以重现由"FULL OUTER JOIN"生成的集合`.为了获得更忠实的仿真,我们需要一个**`UNION ALL`**设置运算符,其中一个查询需要一个*反连接*模式.来自**Pavle Lekic**(上图)的评论给出了*正确的*查询模式. (9认同)
  • 所以不同之处在于我正在进行左包含连接,然后使用UNION*ALL*进行右对齐 (7认同)
  • 这个答案是错误的。它将删除重复的行。 (6认同)
  • 我现在看到你说自己,对不起.也许你可以更新你的答案,因为有这种情况它会出错并且UNION ALL总是会更高效吗? (4认同)
  • @NikolaBogdanović:如果您加入的不是一个独特的关键,那肯定会有所不同.假设t2有两行具有相同的id,而t1具有零或一行具有该id; 您的UNION查询只提供一行结果; 正确的查询`SELECT*FROM t1 LEFT JOIN t2 ON t1.id = t2.id UNION ALL SELECT*FROM t1 RIGHT JOIN t2 ON t1.id = t2.id WHERE t1.id IS NULL`给出两个. (2认同)
  • @The Impaler:这里存在一些矛盾。[最高票答案](/sf/ask/335781071/#9214674)以*“答案开头巴勃罗·圣克鲁斯给出的信息是正确的”*。也许更具体地说明哪些答案和哪些评论支持该主张? (2认同)

Nat*_*ong 339

Pablo Santa Cruz给出的答案是正确的; 但是,如果有人偶然发现了这个页面并想要进一步澄清,这里有一个详细的细分.

示例表

假设我们有以下表格:

-- t1
id  name
1   Tim
2   Marta

-- t2
id  name
1   Tim
3   Katarina
Run Code Online (Sandbox Code Playgroud)

内部联接

内连接,如下所示:

SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Run Code Online (Sandbox Code Playgroud)

只会让我们看到两个表中出现的记录,如下所示:

1 Tim  1 Tim
Run Code Online (Sandbox Code Playgroud)

内连接没有方向(如左或右),因为它们是明确双向的 - 我们需要在两侧都匹配.

外连接

另一方面,外连接用于查找在另一个表中可能没有匹配的记录.因此,您必须指定允许连接的哪一侧具有缺失记录.

LEFT JOINRIGHT JOIN是简写LEFT OUTER JOINRIGHT OUTER JOIN; 我将使用下面的全名来强化外连接与内连接的概念.

左外连接

左外连接,如下所示:

SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Run Code Online (Sandbox Code Playgroud)

...将从左表中获取所有记录,无论它们是否在右表中匹配,如下所示:

1 Tim   1    Tim
2 Marta NULL NULL
Run Code Online (Sandbox Code Playgroud)

正确的外部加入

右外连接,如下所示:

SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Run Code Online (Sandbox Code Playgroud)

...将从右表中获取所有记录,无论左表中是否有匹配,如下所示:

1    Tim   1  Tim
NULL NULL  3  Katarina
Run Code Online (Sandbox Code Playgroud)

完全外部加入

完全外连接将为我们提供来自两个表的所有记录,无论它们是否在另一个表中具有匹配,在没有匹配的情况下两侧都有NULL.结果如下所示:

1    Tim   1    Tim
2    Marta NULL NULL
NULL NULL  3    Katarina
Run Code Online (Sandbox Code Playgroud)

然而,正如Pablo Santa Cruz指出的那样,MySQL并不支持这一点.我们可以通过执行左连接和右连接的UNION来模拟它,如下所示:

SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`

UNION

SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Run Code Online (Sandbox Code Playgroud)

您可以将a UNION视为"运行这两个查询,然后将结果堆叠在一起"; 一些行将来自第一个查询,一些来自第二个查询.

应该注意的是UNION,MySQL 中的a 将消除完全相同的重复:Tim将出现在这两个查询中,但结果UNION只列出了他一次.我的数据库大师同事觉得不应该依赖这种行为.所以为了更加明确,我们可以WHERE在第二个查询中添加一个子句:

SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`

UNION

SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
WHERE `t1`.`id` IS NULL;
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您因某些原因想要查看重复项,可以使用UNION ALL.

  • 这个答案已经有一年多了,但事实证明,阿特伍德先生在2007年的博客上得到了更好的答案:http://www.codinghorror.com/blog/2007/10/a-visual-explanation-的-SQL joins.html (12认同)
  • 对于MySQL,如果没有重叠,你真的想避免使用UNION而不是UNION ALL(参见上面的Pavle评论).如果您可以在答案中添加更多信息,我认为这是这个问题的首选答案,因为它更彻底. (4认同)
  • "数据库大师同事"的建议是正确的.在关系模型方面(Ted Codd和Chris Date完成的所有理论工作),对最后一个表单的查询模拟了一个FULL OUTER JOIN,因为它结合了两个不同的集合,第二个查询没有引入"重复"(第一个查询已经返回的行,这些行不会由`FULL OUTER JOIN`生成.以这种方式执行查询并使用UNION删除这些重复项没有任何问题.但要真正复制"FULL OUTER JOIN",我们需要其中一个查询成为反连接. (2认同)
  • @IstiaqueAhmed:目标是模拟 FULL OUTER JOIN 操作。我们在第二个查询中需要该条件,以便它仅返回不匹配的行(反连接模式)。如果没有该条件,查询就是外连接...它返回匹配的行以及不匹配的行。第一个查询“已经”返回了匹配的行。如果第二个查询(再次)返回相同的行,则我们已经重复了行,并且我们的结果将*不*等于 FULL OUTER JOIN。 (2认同)
  • @IstiaqueAhmed:“UNION”操作确实会删除这些重复项;但它还会删除所有重复行,包括 FULL OUTER JOIN 返回的重复行。要模拟“a FULL JOIN b”,正确的模式是“(a LEFT JOIN b) UNION ALL (b ANTI JOIN a)”。 (2认同)
  • 非常简洁的答案,有很好的解释。谢谢你。 (2认同)

shA*_*A.t 33

使用union查询将删除重复项,这与full outer join从不删除任何重复项的行为不同:

[Table: t1]                            [Table: t2]
value                                  value
-------                                -------
1                                      1
2                                      2
4                                      2
4                                      5
Run Code Online (Sandbox Code Playgroud)

这是预期的结果full outer join:

value | value
------+-------
1     | 1
2     | 2
2     | 2
Null  | 5
4     | Null
4     | Null
Run Code Online (Sandbox Code Playgroud)

这是使用leftright Join使用的结果union:

value | value
------+-------
Null  | 5 
1     | 1
2     | 2
4     | Null
Run Code Online (Sandbox Code Playgroud)

[SQL Fiddle]

我建议的查询是:

select 
    t1.value, t2.value
from t1 
left outer join t2  
  on t1.value = t2.value
union all      -- Using `union all` instead of `union`
select 
    t1.value, t2.value
from t2 
left outer join t1 
  on t1.value = t2.value
where 
    t1.value IS NULL 
Run Code Online (Sandbox Code Playgroud)

上述查询的结果与预期结果相同:

value | value
------+-------
1     | 1
2     | 2
2     | 2
4     | NULL
4     | NULL
NULL  | 5
Run Code Online (Sandbox Code Playgroud)

[SQL Fiddle]


@Steve Chambers:[来自评论,非常感谢!]
注意:这可能是最佳解决方案,无论是效率还是生成相同的结果FULL OUTER JOIN.这篇博文也很好地解释了 - 引用方法2:"这正确地处理重复的行,并且不包括它不应该包含的任何内容.必须使用UNION ALL而不是plain UNION,这将消除我想要保留的重复项.在大型结果集上可能会显着提高效率,因为不需要对重复项进行排序和删除."


我决定添加另一个来自full outer join可视化和数学的解决方案,它不是更好,但更可读:

全外连接方式(t1 ? t2):全部t1t2
(t1 ? t2) = (t1 ? t2) + t1_only + t2_only全部都:t1t2加上所有的t1在没有t2和以及所有在t2不在t1:

-- (t1 ? t2): all in both t1 and t2
select t1.value, t2.value
from t1 join t2 on t1.value = t2.value    
union all  -- And plus 
-- all in t1 that not exists in t2
select t1.value, null
from t1
where not exists( select 1 from t2 where t2.value = t1.value)    
union all  -- and plus
-- all in t2 that not exists in t1
select null, t2.value
from t2
where not exists( select 1 from t1 where t2.value = t1.value)
Run Code Online (Sandbox Code Playgroud)

[SQL Fiddle]

  • 这种方法似乎是最好的解决方案,既可以提高效率,也可以产生与"FULL OUTER JOIN"相同的结果.[此博客文章](http://www.xaprb.com/blog/2006/05/26/how-to-write-full-outer-join-in-mysql)也很好地解释了 - 引用方法2 :*"这正确地处理重复的行,并且不包括它不应该包含的任何内容.必须使用UNION ALL而不是简单的UNION,这将消除我想要保留的重复.这对于大型结果集可能明显更有效. ,因为没有必要排序和删除重复."* (5认同)
  • @SteveChambers为时已晚,但感谢您的评论。我添加了您的评论,然后回答突出显示的更多内容;如果您不同意,请回滚;)。 (2认同)

Gor*_*off 13

前面的答案实际上都不正确,因为当存在重复值时它们不遵循语义。

对于诸如(来自此重复项)的查询:

SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;
Run Code Online (Sandbox Code Playgroud)

正确的等价是:

SELECT t1.*, t2.*
FROM (SELECT name FROM t1 UNION  -- This is intentionally UNION to remove duplicates
      SELECT name FROM t2
     ) n LEFT JOIN
     t1
     ON t1.name = n.name LEFT JOIN
     t2
     ON t2.name = n.name;
Run Code Online (Sandbox Code Playgroud)

如果您需要它来处理NULL值(这可能也是必要的),请使用NULL-safe 比较运算符,<=>而不是=


a20*_*a20 5

为了更加清晰,我修改了shA.t 的查询:

-- t1 left join t2
SELECT t1.value, t2.value
FROM t1 LEFT JOIN t2 ON t1.value = t2.value   

    UNION ALL -- include duplicates

-- t1 right exclude join t2 (records found only in t2)
SELECT t1.value, t2.value
FROM t1 RIGHT JOIN t2 ON t1.value = t2.value
WHERE t1.value IS NULL 
Run Code Online (Sandbox Code Playgroud)


Raj*_*Raj 5

MySql没有FULL-OUTER-JOIN语法。您必须通过执行LEFT JOIN和RIGHT JOIN来模拟,如下所示:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id  
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Run Code Online (Sandbox Code Playgroud)

但是MySql也没有RIGHT JOIN语法。根据MySql的外部联接简化,通过在查询的FROMand ON子句中切换t1和t2,将右联接转换为等效的左联接。因此,MySql Query Optimizer会将原始查询转换为以下内容-

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id  
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
Run Code Online (Sandbox Code Playgroud)

现在,按原样编写原始查询没有什么害处,但是如果您有诸如WHERE子句之类的谓词(这是联接前的谓词),或者该ON子句中的AND谓词(这是联接期间的谓词),那么您可能想看看魔鬼;这是详细信息。

MySql查询优化器会定期检查谓词是否被null拒绝空拒绝定义和示例 现在,如果您已完成RIGHT JOIN,但在t1列上使用WHERE谓词,则可能有陷入空值拒绝情况的风险。

例如,以下查询-

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
Run Code Online (Sandbox Code Playgroud)

由Query Optimizer转换为以下内容-

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
WHERE t1.col1 = 'someValue'
Run Code Online (Sandbox Code Playgroud)

因此,表的顺序已更改,但谓词仍适用于t1,但是t1现在位于“ ON”子句中。如果将t1.col1定义为NOT NULL column,则此查询将为null拒绝

MySql将任何被null拒绝的外部联接(左,右,完整)转换为内部联接。

因此,您可能期望的结果可能与MySql返回的结果完全不同。您可能认为它与MySql的RIGHT JOIN有关,但那是不对的。这就是MySql查询优化器的工作方式。因此,负责开发人员在构建查询时必须注意这些细微差别。