Я хочу зробити повне зовнішнє приєднання до MySQL. Чи можливо це? Чи підтримується MySQL повне зовнішнє приєднання?
Я хочу зробити повне зовнішнє приєднання до MySQL. Чи можливо це? Чи підтримується MySQL повне зовнішнє приєднання?
Відповіді:
У вас немає ПОЛЬНИХ ПРИЄДНАНЬ на MySQL, але ви можете впевнено імітувати їх .
Для коду ЗРАБОК, записаного з цього питання, ви маєте:
з двома таблицями 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
заданого оператора для видалення повторюваних рядків, введених шаблоном запиту. Ми можемо уникнути введення повторюваних рядків, використовуючи шаблон для з'єднання для другого запиту, а потім використовувати оператор набору UNION ALL для об'єднання двох наборів. У більш загальному випадку, коли ПОВНОГО ВИХОДЖЕННЯ ПРИЄДНАЄТЬСЯ повертає повторювані рядки, ми можемо це зробити:
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 ALL
оператор набору, і для одного із запитів потрібна схема з’єднання . Коментар Павла Лекіча (вище) дає правильну схему запитів.
Відповідь Пабло Санта Крус правильна; однак, якщо хтось натрапив на цю сторінку і хоче більше роз'яснень, ось детальний розбір.
Припустимо, у нас є такі таблиці:
-- 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
.
FULL OUTER JOIN
. Немає нічого поганого в тому, щоб робити запити таким чином і використовувати UNION для видалення цих дублікатів. Але для того, щоб насправді реплікувати FULL OUTER JOIN
, нам потрібен один із запитів, щоб бути антиприєднанням.
UNION
операція видалить ці дублікати; але він також видаляє ВСІ дублікати рядків, у тому числі повторювані рядки, які були б повернуті ПОВНІШНІМ ВИКОРИСТАННЯМ. Щоб наслідувати 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
замість прості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)
FULL OUTER JOIN
. Ця публікація в блозі також це добре пояснює - цитую з Методу 2: "Це обробляє дублікати рядків правильно і не включає нічого, що не повинно. Необхідно використовувати UNION ALL замість простого UNION, що усуне дублікати, які я хочу Це може бути значно ефективніше для великих наборів результатів, оскільки немає необхідності сортувати та видаляти дублікати. "
MySql не має синтаксису ПОВНОГО ВИХІД-ПРИЄДНАЙТЕСЯ. Ви повинні наслідувати, виконуючи і СПІЛЬНИЙ ПРИЄДНАЙТЕСЬ, і ПРАВИЛЬНИЙ ПРИЄДНУЙСЯ,
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Але MySql також не має синтаксису ПРАВО ПРИЄДНАЙТЕСЬ. Згідно із спрощенням зовнішнього приєднання MySql , право приєднання перетворюється на еквівалентне ліве з'єднання шляхом перемикання t1 і t2 в FROM
і ON
в запиті. Таким чином, оптимізатор запитів 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, який є предикатом перед приєднанням або предикатом AND на ON
пункті, який є предикатом під час приєднання , тоді ви можливо, захоче поглянути на чорта; що в деталях.
Оптимізатор запитів MySql регулярно перевіряє предикати, чи не відхилено їх . Тепер, якщо ви зробили ПРАВИЛЬНЕ ПРИЄДНАННЯ, але з тим, де предикат на стовпчик від 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
стовпець, то цей запит буде відхилено нулем .
Будь-яке зовнішнє з'єднання (ліве, праве, повне), яке відхилене від нуля , перетворюється на внутрішнє з'єднання MySql.
Таким чином, результати, які ви можете очікувати, можуть бути абсолютно іншими, ніж повертається MySql. Ви можете подумати, що це помилка з ПРАВИМОЮ ПРИЄДНАЙТЕСЬ до MySql, але це не так. Просто так працює оптимізатор запитів 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
стовпець є недійсним. union all
Запит з анти-Join шаблоном повинен відтворити зовнішнє з'єднання поведінки правильно, але яке рішення є більш відповідним , залежить від контексту і від обмежень, які діють на столах.
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
що ви сказали про рішення для приєднання Cross ?
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON 1=1;
select (select count(*) from t1) * (select count(*) from t2))
рядками в наборі результатів.
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 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 не охоплює крайного випадку, коли базові таблиці мають дублювані записи.
Пояснення:
Є крайній випадок, який не може охопити Спілка чи Союз. Ми не можемо перевірити це на 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
Порядок інший, але в іншому випадку відповідає правильній відповіді.
UNION ALL
рішення. Крім того, у ньому представлено рішення, UNION
яке використовуватиме повільніше на великих вихідних таблицях через необхідне дедублювання. Нарешті, вона не буде компілюватися, оскільки поля id
не існує в підзапиті tmp
.
UNION ALL
Рішення: ... також неправильне." Код, який ви подаєте, не виключає перетину-виключення з права з'єднання ( where t1.id1 is null
), яке повинно бути вказано в UNION ALL
. Що означає, що ваше рішення перемагає всі інші, лише коли одне з цих рішень неправильно реалізовано. Про "милості", взяті точки. Це було виправдано, мої вибачення.
Стандарт 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
результат не може мати нуль у певному правильному стовпчику таблиці, тоді " right join on
рядки не inner join on
є" - це рядки right join on
із on
умовою, розширеним and
цим стовпцем is null
.
Тобто аналогічні right join on
union all
відповідні left join on
ряди.
Від чого відмінність "ВНУТРІШНЯ ПРИЄДНАЙТЕСЬ" та "ВНУТРІШНЯ ПРИЄДНАЙТЕСЬ"? :
(SQL Standard 2006 SQL / Foundation 7.7 Синтаксичні правила 1, Загальні правила 1 b, 3 c & d, 5 b.)