MySQLで完全な外部結合を実行したいと思います。これは可能ですか?完全な外部結合はMySQLでサポートされていますか?
15 に答える
MySQLには完全結合はありませんが、確実にエミュレートできます。
このStackOverflowの質問から転記されたコードサンプルの場合、次のようになります。
2つのテーブル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
上記のクエリは、完全外部結合操作で重複行が生成されない特殊なケースで機能します。上記のクエリはUNION
、クエリパターンによって導入された重複行を削除するために集合演算子に依存しています。2番目のクエリに反結合パターンを使用して重複行を導入することを回避し、次にUNIONALL集合演算子を使用して2つの集合を結合します。より一般的なケースでは、完全外部結合が重複行を返す場合、これを行うことができます。
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
パブロサンタクルスが出した答えは正しいです。ただし、誰かがこのページを偶然見つけて、さらに明確にしたい場合に備えて、ここに詳細な内訳を示します。
表の例
次のテーブルがあるとします。
-- 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`;
UNION
aは、「これらのクエリの両方を実行してから、結果を互いに積み重ねる」という意味と考えることができます。一部の行は最初のクエリから取得され、一部は2番目のクエリから取得されます。
UNION
MySQLのaは正確な重複を排除することに注意してください。Timはここの両方のクエリに表示されますが、結果UNION
は彼を1回だけリストします。私のデータベースの第一人者の同僚は、この振る舞いに頼るべきではないと感じています。したがって、それについてより明確にするWHERE
ために、2番目のクエリに句を追加できます。
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
場合は、を使用できます。
ユニオンクエリを使用すると重複が削除されます。これは、重複を削除しない完全外部結合の動作とは異なります。
[Table: t1] [Table: t2]
value value
----------- -------
1 1
2 2
4 2
4 5
これは、完全な外部結合の予想される結果です。
value | value
------+-------
1 | 1
2 | 2
2 | 2
Null | 5
4 | Null
4 | Null
これは、ユニオンで左結合と右結合を使用した結果です。
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
プレーンの代わりに使用する必要がありますUNION
。これにより、保持したい重複がなくなります。これは重複を並べ替えて削除する必要がないため、大きな結果セットでは大幅に効率が上がる可能性があります。」
完全な外部結合の視覚化と数学から得られる別のソリューションを追加することにしました。上記よりも優れているわけではありませんが、より読みやすくなっています。
完全な外部結合とは、
(t1 ∪ t2)
:all int1
またはint2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
:all inbotht1
およびt2
plusallint1
that is not int2
and plus all int2
that is not int1
:を意味します。
-- (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構文がありません。次のように、LEFTJOINとRIGHTJOINの両方を実行してエミュレートする必要があります。
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
ただし、MySQLにはRIGHTJOIN構文もありません。MySQLの外部結合の簡略化FROM
によると、クエリのandON
句のt1とt2を切り替えることにより、右結合が同等の左結合に変換されます。したがって、MySQLクエリオプティマイザは元のクエリを次のように変換します-
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述部を使用した場合、nullが拒否されたシナリオに遭遇するリスクがある可能性があります。
たとえば、クエリ
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'
クエリオプティマイザによって次のように変換されます。
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
列として定義されている場合、このクエリはnullで拒否されます。
nullが拒否された外部結合(左、右、完全)は、MySQLによって内部結合に変換されます。
したがって、期待する結果は、MySQLが返す結果とは完全に異なる可能性があります。MySQLのRIGHTJOINのバグだと思うかもしれませんが、そうではありません。MySQLクエリオプティマイザがどのように機能するか。したがって、担当の開発者は、クエリを作成するときにこれらのニュアンスに注意を払う必要があります。
重複する値がある場合、それらはセマンティクスに従わないため、前の回答はどれも実際には正しくありません。
(この複製から)のようなクエリの場合:
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比較演算子を使用してください。<=>
=
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
より明確にするために、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
次のことができます。
(SELECT
*
FROM
table1 t1
LEFT JOIN
table2 t2 ON t1.id = t2.id
WHERE
t2.id IS NULL)
UNION ALL
(SELECT
*
FROM
table1 t1
RIGHT JOIN
table2 t2 ON t1.id = t2.id
WHERE
t1.id IS NULL);
SELECT
a.name,
b.title
FROM
author AS a
LEFT JOIN
book AS b
ON a.id = b.author_id
UNION
SELECT
a.name,
b.title
FROM
author AS a
RIGHT JOIN
book AS b
ON a.id = b.author_id
完全な外部結合を変換できます。例:
SELECT fields
FROM firsttable
FULL OUTER JOIN secondtable ON joincondition
の中へ:
SELECT fields
FROM firsttable
LEFT JOIN secondtable ON joincondition
UNION ALL
SELECT fields (replacing any fields from firsttable with NULL)
FROM secondtable
WHERE NOT EXISTS (SELECT 1 FROM firsttable WHERE joincondition)
または、少なくとも1つの列がある場合、たとえばfoo
、firsttable
それがNULLでない場合は、次のように実行できます。
SELECT fields
FROM firsttable
LEFT JOIN secondtable ON joincondition
UNION ALL
SELECT fields
FROM firsttable
RIGHT JOIN secondtable ON joincondition
WHERE firsttable.foo IS NULL
私は応答を修正し、作業にはすべての行が含まれます(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
);
使用する:
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またはUNIONALLの回答を使用しても、ベーステーブルのエントリが重複しているエッジケースはカバーされません。
説明:
UNIONまたはUNIONALLではカバーできないエッジケースがあります。MySQLは完全外部結合をサポートしていないため、これをテストすることはできませんが、サポートしているデータベースでこれを説明することはできます。
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
順序は異なりますが、それ以外は正解と一致します。
クロスジョインソリューションを使用します。
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON 1=1;
それも可能ですが、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
SQL標準によるfull join on
と、一致しない左側のテーブル行がnullで拡張され、inner join on
右側のテーブル行がnullで拡張されます。つまり、行は行になりますが、行はありません。union all
union all
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
、特定の右側のテーブル列で結果にnullを含めることができないことがわかっている場合、「right join on
行ではないinner join on
」は、その列によって条件が拡張されright join on
た行です。on
and
is null
つまり、同様にright join on
union all
適切なleft join on
行です。
「 INNERJOIN 」と「OUTERJOIN」の違いは何ですか?:
(SQL Standard 2006 SQL / Foundation 7.7構文規則1、一般規則1 b、3 c&d、5 b)