我想在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操作不会产生任何重复行的特殊情况。上面的查询取决于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
(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 ALLset运算符,而其中一个查询将需要一个反联接模式。来自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的外部联接简化,通过在查询的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
现在,按原样编写原始查询没有什么害处,但是说,如果您有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扩展条件的行andis 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。)