我想在MySQL中进行完全外部联接。这可能吗?MySQL是否支持完全外部联接?
我想在MySQL中进行完全外部联接。这可能吗?MySQL是否支持完全外部联接?
Answers:
您在MySQL上没有FULL JOINS,但是可以肯定地模拟它们。
对于从该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
上面的查询适用于FULL OUTER JOIN操作不会产生任何重复行的特殊情况。上面的查询取决于UNION
set运算符,以删除查询模式引入的重复行。我们可以通过对第二个查询使用反联接模式来避免引入重复的行,然后使用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
(SELECT ... FROM tbl1 LEFT JOIN tbl2 ...) UNION ALL (SELECT ... FROM tbl1 RIGHT JOIN tbl2 ... WHERE tbl1.col IS NULL)
t1
和t2
,在这个答案的查询并返回一个结果,它模拟FULL OUTER JOIN。但是,在更一般的情况下,例如SELECT列表没有足够的列/表达式来使返回的行唯一,那么此查询模式不足以再现由a产生的集合FULL OUTER JOIN
。为了获得更真实的仿真,我们需要一个UNION ALL
set运算符,而其中一个查询将需要一个反联接模式。来自Pavle Lekic的评论(上面)给出了正确的查询模式。
Pablo Santa Cruz给出的答案是正确的。但是,如果有人在此页面上跌跌撞撞,想要进一步澄清,请按以下详细分类。
假设我们有下表:
-- t1
id name
1 Tim
2 Marta
-- t2
id name
1 Tim
3 Katarina
内部联接,如下所示:
SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
只会让我们出现在两个表中的记录,如下所示:
1 Tim 1 Tim
内连接没有方向(如左或右),因为它们明确地是双向的-我们需要双方都匹配。
另一方面,外部联接用于查找其他表中可能没有匹配项的记录。这样,您必须指定允许连接的哪一侧具有丢失的记录。
LEFT JOIN
和RIGHT JOIN
被简写LEFT OUTER JOIN
和RIGHT OUTER JOIN
; 我将在下面使用它们的全名来增强外部联接与内部联接的概念。
左外部联接,如下所示:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
...将使我们从左表获得所有记录,无论它们在右表中是否匹配,如下所示:
1 Tim 1 Tim
2 Marta NULL NULL
右外部联接,如下所示:
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
...将使我们从右表获得所有记录,无论它们在左表中是否匹配,如下所示:
1 Tim 1 Tim
NULL NULL 3 Katarina
完全外部联接将为我们提供两个表中的所有记录,无论它们是否在另一个表中都有匹配项,并且在两端都没有匹配项的情况下都为NULL。结果将如下所示:
1 Tim 1 Tim
2 Marta NULL NULL
NULL NULL 3 Katarina
但是,正如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`;
您可以将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;
另一方面,如果您出于某些原因想要查看重复项,则可以使用UNION ALL
。
FULL OUTER JOIN
。用这种方式进行查询并使用UNION删除这些重复项没有什么错。但是要真正复制a FULL OUTER JOIN
,我们需要其中一个查询是一个反联接。
UNION
操作将删除这些重复项;但它也会删除所有重复行,包括FULL OUTER JOIN返回的重复行。要模拟a FULL JOIN b
,正确的模式是(a LEFT JOIN b) UNION ALL (b ANTI JOIN a)
。
使用union
查询将删除重复项,这与full outer join
从不删除任何重复项的行为不同:
[Table: t1] [Table: t2]
value value
------- -------
1 1
2 2
4 2
4 5
这是预期的结果full outer join
:
value | value
------+-------
1 | 1
2 | 2
2 | 2
Null | 5
4 | Null
4 | Null
这是使用left
和right Join
结合使用的结果union
:
value | value
------+-------
Null | 5
1 | 1
2 | 2
4 | Null
我建议的查询是:
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
以上查询的结果与预期结果相同:
value | value
------+-------
1 | 1
2 | 2
2 | 2
4 | NULL
4 | NULL
NULL | 5
@Steve Chambers:[来自评论,非常感谢!]
注意:这可能是最好的解决方案,无论是为了提高效率还是产生与都相同的结果FULL OUTER JOIN
。这篇博客文章也很好地解释了这一点-引用方法2:“此方法可以正确处理重复的行,并且不包含任何不应该包含的内容。有必要使用UNION ALL
而不是plainUNION
来消除我要保留的重复项。这由于不需要分类和删除重复项,因此在大型结果集上可能会显着提高效率。”
我决定添加另一个解决方案 full outer join
可视化和数学的,它不是上面的更好,但更具可读性:
完全外部联接的意思是
(t1 ∪ t2)
:全部位于t1
或之中t2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
:全部位于两者之中t1
,t2
以及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)
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
但是MySql也没有RIGHT JOIN语法。根据MySql的外部联接简化,通过在查询的FROM
and 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
现在,按原样编写原始查询没有什么害处,但是说,如果您有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'
由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'
因此,表的顺序已更改,但谓词仍适用于t1,但是t1现在位于“ ON”子句中。如果将t1.col1定义为NOT NULL
column,则此查询将为null拒绝。
MySql将任何被null拒绝的外部联接(左,右,完整)转换为内部联接。
因此,您可能期望的结果可能与MySql返回的结果完全不同。您可能认为它与MySql的RIGHT JOIN有关,但那是不对的。这就是MySql查询优化器的工作方式。因此,负责开发人员在构建查询时必须注意这些细微差别。
在SQLite中,您应该这样做:
SELECT *
FROM leftTable lt
LEFT JOIN rightTable rt ON lt.id = rt.lrid
UNION
SELECT lt.*, rl.* -- To match column set
FROM rightTable rt
LEFT JOIN leftTable lt ON lt.id = rt.lrid
上述答案实际上都不是正确的,因为当存在重复的值时,它们不遵循语义。
对于诸如(来自此重复项)的查询:
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;
正确的等效项是:
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;
如果您需要使用它来处理NULL
值(可能也有必要),请使用NULL
-safe比较运算符,<=>
而不是=
。
FULL OUTER JOIN
当name
列为null 时,结果可能会与a有所不同。union all
具有反联接模式的查询应该正确地重现外部联接行为,但是哪种解决方案更合适取决于上下文和表上活动的约束。
union all
,但是该答案在第一个查询或第二个查询中都遗漏了一个反联接模式,该模式将保留现有重复项,但阻止添加新重复项。根据上下文,其他解决方案(例如此解决方案)可能更合适。
修改了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 t2.value IS NULL
也可以,但必须在select中提及相同的字段名称。
SELECT t1.name, t2.name FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT t1.name, t2.name FROM t2
LEFT JOIN t1 ON t1.id = t2.id
我修复了响应,并包括了所有行(基于Pavle Lekic的响应)
(
SELECT a.* FROM tablea a
LEFT JOIN tableb b ON a.`key` = b.key
WHERE b.`key` is null
)
UNION ALL
(
SELECT a.* FROM tablea a
LEFT JOIN tableb b ON a.`key` = b.key
where a.`key` = b.`key`
)
UNION ALL
(
SELECT b.* FROM tablea a
right JOIN tableb b ON b.`key` = a.key
WHERE a.`key` is null
);
tablea
没有匹配项的行,tableb
反之亦然。您尝试使用UNION ALL
,只有在这两个表具有同等顺序的列(这不能保证)时,它才起作用。
回答:
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id;
可以重新创建如下:
SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;
使用UNION或UNION ALL答案不能解决基本表具有重复条目的情况。
说明:
在某些情况下,UNION或UNION ALL无法覆盖。我们无法在mysql上对其进行测试,因为它不支持FULL OUTER JOINs,但我们可以在支持它的数据库中对此进行说明:
WITH cte_t1 AS
(
SELECT 1 AS id1
UNION ALL SELECT 2
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 6
),
cte_t2 AS
(
SELECT 3 AS id2
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 6
)
SELECT * FROM cte_t1 t1 FULL OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2;
This gives us this answer:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
UNION解决方案:
SELECT * FROM cte_t1 t1 LEFT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
UNION
SELECT * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
给出错误的答案:
id1 id2
NULL 3
NULL 4
1 NULL
2 NULL
5 5
6 6
UNION ALL解决方案:
SELECT * FROM cte_t1 t1 LEFT OUTER join cte_t2 t2 ON t1.id1 = t2.id2
UNION ALL
SELECT * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
也不正确。
id1 id2
1 NULL
2 NULL
5 5
6 6
6 6
6 6
6 6
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
而此查询:
SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;
提供以下内容:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
顺序不同,但是与正确答案匹配。
UNION ALL
解决方案。此外,它提出了一种解决方案UNION
,由于需要重复数据删除,因此在大型源表上使用该解决方案的速度会较慢。最后,它不会编译,因为id
子查询中不存在该字段tmp
。
UNION ALL
解决方案:...也不正确。” 您提供的代码省略了右联接(where t1.id1 is null
必须在中提供)中UNION ALL
。也就是说,仅当其他解决方案之一实施不正确时,您的解决方案才胜过其他所有解决方案。关于“可爱”,要点。抱歉,这是免费的。
SQL标准说 full join on
是inner join on
行union all
不匹配左表行扩展为null union all
右表行扩展为null。即inner join on
行中的union all
行,left join on
但inner join on
union all
行中的行right join on
不是inner join on
。
即 left join on
行union all
right join on
的行不inner join on
。或者,如果您知道inner join on
结果在特定的右表列中不能为空,则“ right join on
不在其中inner join on
的行”是right join on
具有该列on
扩展条件的行and
is null
。
即同样right join on
union all
合适left join on
行。
从有什么区别“INNER JOIN”和“OUTER JOIN”有什么区别?:
(SQL Standard 2006 SQL / Foundation 7.7语法规则1,通用规则1b,3c和d,5b。)