Как сделать FULL OUTER JOIN в MySQL? - PullRequest
568 голосов
/ 25 января 2011

Я хочу сделать полное внешнее соединение в MySQL.Это возможно?Поддерживается ли полное внешнее соединение MySQL?

Ответы [ 14 ]

574 голосов
/ 25 января 2011

У вас нет ПОЛНЫХ СОЕДИНЕНИЙ на MySQL, но вы можете быть уверены, что подражает им .

Для кода ОБРАЗЕЦ расшифровывается с этот вопрос SO у вас есть:

с двумя таблицами 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 для удаления дублирующихся строк, введенных шаблоном запроса. Мы можем избежать появления повторяющихся строк, используя шаблон anti-join для второго запроса, а затем используя оператор множеств 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
328 голосов
/ 09 февраля 2012

Ответ, который Пабло Санта-Крус дал, является правильным; однако, в случае, если кто-то наткнулся на эту страницу и хочет получить больше разъяснений, вот подробное описание.

Примеры таблиц

Предположим, у нас есть следующие таблицы:

-- 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

Однако, как отметил Пабло Санта Круз, MySQL не поддерживает это. Мы можем эмулировать его, выполнив СОЮЗ левого соединения и правого соединения, например:

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 как о значении «выполнить оба этих запроса, а затем поместить результаты друг на друга»; некоторые строки будут получены из первого запроса, а некоторые из второго.

Следует отметить, что UNION в MySQL устранит точные дубликаты: Тим появится здесь в обоих запросах, но результат 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.

30 голосов
/ 28 мая 2015

Использование запроса 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

[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 

Результат вышеупомянутого запроса совпадает с ожидаемым:

value | value
------+-------
1     | 1
2     | 2
2     | 2
4     | NULL
4     | NULL
NULL  | 5

[SQL Fiddle]


@ Стив Чамберс : [Из комментариев, большое спасибо!]
Примечание: Это может быть лучшим решением как для эффективности, так и для получения тех же результатов, что и FULL OUTER JOIN. Этот пост в блоге также хорошо объясняет это - цитируя из метода 2: "Это правильно обрабатывает дублирующиеся строки и не включает в себя ничего, что не должно. Необходимо использовать UNION ALL вместо простогоUNION, что устранит дубликаты, которые я хочу сохранить. Это может быть значительно более эффективно для больших наборов результатов, поскольку нет необходимости сортировать и удалять дубликаты. "


Я решил добавить другое решение, основанное на 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)

[SQL Fiddle]

4 голосов
/ 12 октября 2017

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 *1005*, правое объединение преобразуется в эквивалентное левое объединение путем переключения t1 и t2 в предложении FROM и ON в запросе.Таким образом, 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, котороепредикат перед соединением или предикат AND в предложении ON, который является предикатом во время соединения , затем вывозможно, захотите взглянуть на дьявола;который находится в деталях.

Оптимизатор запросов MySql регулярно проверяет предикаты, если они отклонены null .Null-Rejected Definition and Examples Теперь, если вы выполнили ПРАВИЛЬНОЕ СОЕДИНЕНИЕ, но с предикатом WHERE для столбца от t1, то вы можете столкнуться с сценарием отклоненного с нуля .

Например, следующий запрос -

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-rejected преобразуется во внутреннее соединение MySql.

Таким образом, ожидаемые результаты могут полностью отличаться от результатов MySql.Вы можете подумать, что это ошибка с правым соединением MySql, но это не так.Просто так работает оптимизатор запросов MySql.Поэтому ответственный разработчик должен обращать внимание на эти нюансы при создании запроса.

4 голосов
/ 14 января 2017

Ни один из приведенных выше ответов на самом деле не является правильным, поскольку они не следуют семантике при наличии дублированных значений.

Для запроса, такого как (из этого дубликата ):

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, безопасный вместо <=>.

4 голосов
/ 14 января 2013

В 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
3 голосов
/ 05 июня 2018

Вы можете сделать следующее:

(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);
3 голосов
/ 11 марта 2016

Изменен запрос 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 
0 голосов
/ 12 августа 2018

Стандарт SQL гласит: full join on - это inner join on строки 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 строки.

с В чем разница между «INNER JOIN» и «OUTER JOIN»?:

(SQL Standard 2006 SQL / Foundation 7.7 Синтаксические правила 1, Общие правила 1 b, 3 c & d, 5 b.)

0 голосов
/ 13 июня 2017

что бы вы сказали о решении для кросс-соединения ?

SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2 
ON 1=1;
...