Как сделать FULL OUTER JOIN в MySQL?

389

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

  • 2
    возможный дубликат MySQL Full Outer Join Синтаксическая Ошибка
  • 3
    На этот вопрос есть лучшие ответы
Показать ещё 1 комментарий
Теги:
join
outer-join
full-outer-join

13 ответов

415
Лучший ответ

У вас нет FULL JOINS для MySQL, но вы можете эмулировать их.

Для кода SAMPLE, транскрибируемого из этого 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 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
  • 27
    На самом деле то, что вы написали, не правильно. Потому что когда вы делаете UNION, вы удаляете дубликаты, а иногда, когда вы объединяете две разные таблицы, должны быть дубликаты.
  • 144
    Это правильный пример: (SELECT ... FROM tbl1 LEFT JOIN tbl2 ...) UNION ALL (SELECT ... FROM tbl1 RIGHT JOIN tbl2 ... WHERE tbl1.col IS NULL)
Показать ещё 19 комментариев
259

Ответ, который 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

Right Outer Join

Прямое внешнее соединение, например:

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 не поддерживает это. Мы можем эмулировать его, выполнив 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 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.

  • 12
    Этому ответу больше года, но оказывается, что у г-на Этвуда был еще лучший ответ в его блоге в 2007 году: codinghorror.com/blog/2007/10/…
  • 4
    Для MySQL вы действительно хотите избегать использования UNION вместо UNION ALL, если нет перекрытия (см. Комментарий Павла выше). Если бы вы могли добавить больше информации об этом в своем ответе здесь, я думаю, что это был бы предпочтительный ответ на этот вопрос, поскольку он более тщательный.
Показать ещё 8 комментариев
22

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


@Steve Chambers: [Из комментариев, с большим благодарением!]
Примечание. Это может быть лучшим решением как для эффективности, так и для получения тех же результатов, что и full outer join. Это сообщение в блоге также хорошо объясняет это - процитировать из метода 2: "Это правильно обрабатывает повторяющиеся строки и не включает в себя что-либо, что ему не нужно. используйте UNION ALL вместо plain 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]

  • 0
    Мы выполняем одно и то же время буксировки задачи. Если для t1 и t2 существуют подзапросы, то mysql должен выполнять одну и ту же задачу чаще, не так ли? Можем ли мы удалить это, используя псевдоним в этой ситуации?
  • 0
    Я предлагаю вам использовать несколько временных таблиц;).
Показать ещё 3 комментария
5

В 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
  • 0
    Можем ли мы использовать это? например, как: SELECT * FROM leftTable lt СЛЕДУЮЩАЯ ПОДПИСКА ;
  • 0
    да, но SQLite не поддерживает правильные объединения, но да, в MYSQL да
3

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

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

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 -серверный оператор сравнения <=>, а не =.

  • 2
    это часто хорошее решение, но оно может дать результаты, отличные от FULL OUTER JOIN когда столбец name пуст. Запрос на union all с шаблоном анти-объединения должен правильно воспроизводить поведение внешнего объединения, но какое решение является более подходящим, зависит от контекста и ограничений, которые активны в таблицах.
  • 0
    @fthiella. , , Это хороший момент. Я поправил ответ.
Показать ещё 4 комментария
3

Измененный запрос 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 
1

что вы сказали об этом решении?

SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2 
ON 1=1;
  • 2
    Нет, это перекрестное соединение. Он будет сопоставлять каждую строку в t1 с каждой строкой в t2, давая набор всех возможных комбинаций, с select (select count(*) from t1) * (select count(*) from t2)) строк в наборе результатов.
  • 0
    Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, как и почему он решает проблему, улучшит долгосрочную ценность ответа.
Показать ещё 1 комментарий
1
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
0

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 упрощение внешнего соединения, правое соединение преобразуется в эквивалентное левое соединение, переключая 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, которое является pre-join или предикат ИИ в предложении ON, который является во время соединения предикат, тогда вы можете взглянуть на дьявола; который находится в деталях.

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

Например, Следующий запрос -   SELECT * FROM t1   LEFT JOIN t2 ON t1.id = t2.id   WHERE t1.col1 = 'someValue'   UNION   SELECT * FROM t1   ПРАВОЕ СОЕДИНЕНИЕ t2 ВКЛ. 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 столбец, тогда этот запрос будет отклонен с ошибкой.

Любое внешнее объединение (левое, правое, полное), которое отклоняется от нуля, преобразуется во внутреннее соединение MySql.

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

0

Я исправляю ответ, и работа включает все строки (на основе ответа Павле Лекича)

    (
    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
    );
  • 0
    Нет, это тип соединения "only-only", которое будет возвращать только те строки tablea которые не совпадают в tableb и наоборот. Вы пытаетесь UNION ALL , что будет работать, только если эти две таблицы имеют одинаково упорядоченные столбцы, что не гарантируется.
  • 0
    это работает, я создаю на временной базе данных tablea (1,2,3,4,5,6) и tableb (4,5,6,7,8,9) его строки имеют 3 столбца "id", "number" и "name_number" как текст, и работает только в результате (1,2,3,7,8,9)
Показать ещё 3 комментария
0

Mysql как таковой не поддерживает команду FULL OUTER JOIN. Три поддерживаемых соединения - INNER JOIN, LEFT JOIN и RIGHT JOIN.

Однако вы можете реализовать полное внешнее соединение, используя команду UNION as
(запрос левого соединения) UNION (запрос прямого соединения)

Например, рассмотрим следующий пример, в котором у меня есть два студента и метки таблиц. Чтобы выполнить полное внешнее соединение, я бы выполнил следующий код:

SELECT * FROM students  
LEFT JOIN marks   
ON students.id = marks.id  
UNION ALL  
SELECT * FROM students 
RIGHT JOIN marks  
ON students.id = marks.id;
0

Это также возможно, но вы должны указать те же имена полей в 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
  • 0
    Это просто дублирует результаты левого соединения.
-1

Ответ:

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

Порядок отличается, но в противном случае соответствует правильному ответу.

  • 0
    Это мило, но искажает решение UNION ALL . Кроме того, он представляет решение с использованием UNION которое будет медленнее для больших исходных таблиц из-за необходимой дедупликации. Наконец, он не будет компилироваться, потому что id поля не существует в подзапросе tmp .
  • 0
    Я никогда не претендовал на скорость, и ОП не упоминал о скорости. Предполагая, что UNION ALL (вы не указываете, какой именно) и оба дают правильный ответ, если бы мы хотели сделать утверждение, что один из них быстрее, нам нужно предоставить эталонные тесты, и это будет отклонением от OP вопрос.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню