How can I do a FULL OUTER JOIN in MySQL?
You don't have full joins in MySQL, but you can sure emulate them.
For a code sample transcribed from this Stack Overflow question you have:
With two tables t1, t2:
SELECT * FROM t1LEFT JOIN t2 ON t1.id = t2.idUNIONSELECT * FROM t1RIGHT JOIN t2 ON t1.id = t2.id
The query above works for special cases where a full outer join operation would not produce any duplicate rows. The query above depends on the UNION
set operator to remove duplicate rows introduced by the query pattern. We can avoid introducing duplicate rows by using an anti-join pattern for the second query, and then use a UNION ALL set operator to combine the two sets. In the more general case, where a full outer join would return duplicate rows, we can do this:
SELECT * FROM t1LEFT JOIN t2 ON t1.id = t2.idUNION ALLSELECT * FROM t1RIGHT JOIN t2 ON t1.id = t2.idWHERE t1.id IS NULL
The answer that Pablo Santa Cruz gave is correct; however, in case anybody stumbled on this page and wants more clarification, here is a detailed breakdown.
Example Tables
Suppose we have the following tables:
-- t1id name1 Tim2 Marta-- t2id name1 Tim3 Katarina
Inner Joins
An inner join, like this:
SELECT *FROM `t1`INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Would get us only records that appear in both tables, like this:
1 Tim 1 Tim
Inner joins don't have a direction (like left or right) because they are explicitly bidirectional - we require a match on both sides.
Outer Joins
Outer joins, on the other hand, are for finding records that may not have a match in the other table. As such, you have to specify which side of the join is allowed to have a missing record.
LEFT JOIN
and RIGHT JOIN
are shorthand for LEFT OUTER JOIN
and RIGHT OUTER JOIN
; I will use their full names below to reinforce the concept of outer joins vs inner joins.
Left Outer Join
A left outer join, like this:
SELECT *FROM `t1`LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
...would get us all the records from the left table regardless of whether or not they have a match in the right table, like this:
1 Tim 1 Tim2 Marta NULL NULL
Right Outer Join
A right outer join, like this:
SELECT *FROM `t1`RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
...would get us all the records from the right table regardless of whether or not they have a match in the left table, like this:
1 Tim 1 TimNULL NULL 3 Katarina
Full Outer Join
A full outer join would give us all records from both tables, whether or not they have a match in the other table, with NULLs on both sides where there is no match. The result would look like this:
1 Tim 1 Tim2 Marta NULL NULLNULL NULL 3 Katarina
However, as Pablo Santa Cruz pointed out, MySQL doesn't support this. We can emulate it by doing a UNION of a left join and a right join, like this:
SELECT *FROM `t1`LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`UNIONSELECT *FROM `t1`RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
You can think of a UNION
as meaning "run both of these queries, then stack the results on top of each other"; some of the rows will come from the first query and some from the second.
It should be noted that a UNION
in MySQL will eliminate exact duplicates: Tim would appear in both of the queries here, but the result of the UNION
only lists him once. My database guru colleague feels that this behavior should not be relied upon. So to be more explicit about it, we could add a WHERE
clause to the second query:
SELECT *FROM `t1`LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`UNIONSELECT *FROM `t1`RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`WHERE `t1`.`id` IS NULL;
On the other hand, if you wanted to see duplicates for some reason, you could use UNION ALL
.
Using a union query will remove duplicates, and this is different than the behavior of full outer join that never removes any duplicates:
[Table: t1] [Table: t2]value value----------- -------1 12 24 24 5
This is the expected result of a full outer join:
value | value------+-------1 | 12 | 22 | 2Null | 54 | Null4 | Null
This is the result of using left and right join with union:
value | value------+-------Null | 51 | 12 | 24 | Null
My suggested query is:
select t1.value, t2.valuefrom t1left outer join t2 on t1.value = t2.valueunion all -- Using `union all` instead of `union`select t1.value, t2.valuefrom t2left outer join t1 on t1.value = t2.valuewhere t1.value IS NULL
The result of the above query that is as the same as the expected result:
value | value------+-------1 | 12 | 22 | 24 | NULL4 | NULLNULL | 5
@Steve Chambers: [From comments, with many thanks!]
Note: This may be the best solution, both for efficiency and for generating the same results as a FULL OUTER JOIN
. This blog post also explains it well - to quote from Method 2: "This handles duplicate rows correctly and doesn’t include anything it shouldn’t. It’s necessary to use UNION ALL
instead of plain UNION
, which would eliminate the duplicates I want to keep. This may be significantly more efficient on large result sets, since there’s no need to sort and remove duplicates."
I decided to add another solution that comes from full outer join visualization and math. It is not better than the above, but it is more readable:
Full outer join means
(t1 ∪ t2)
: all int1
or int2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
: all in botht1
andt2
plus all int1
that aren't int2
and plus all int2
that aren't int1
:
-- (t1 ∩ t2): all in both t1 and t2select t1.value, t2.valuefrom t1 join t2 on t1.value = t2.valueunion all -- And plus-- all in t1 that not exists in t2select t1.value, nullfrom t1where not exists( select 1 from t2 where t2.value = t1.value)union all -- and plus-- all in t2 that not exists in t1select null, t2.valuefrom t2where not exists( select 1 from t1 where t2.value = t1.value)