* FULL JOIN в MySQL
Публикувано на 20 май 2009 в раздел Бази от Данни.
След като научихме заявките, използващи UNION, вече сме готови да посочим как се прави и липсващия в MySQL FULL JOIN. Ще използваме таблиците с плодове и зеленчуци от предишната статия.
Нека припомним как работеха LEFT и RIGHT JOIN. За целта ще направим многотаблична заявка по колоната "цена":
SELECT * FROM vegetables LEFT JOIN fruits ON vegetables.price = fruits.price; +----+------------+-------+------+--------+-------+ | id | name | price | id | name | price | +----+------------+-------+------+--------+-------+ | 1 | krastavici | 4 | NULL | NULL | NULL | | 2 | domati | 5 | NULL | NULL | NULL | | 3 | kartofi | 3 | 1 | banani | 3 | +----+------------+-------+------+--------+-------+ 3 rows in set (0.00 sec)
Резултата е точно това, което очаквахме - само banani имат една и съща цена с kartofi и затова от втората таблица само един ред не е с резултат NULL. В RIGHT JOIN се случваше същото, но в обратна посока:
SELECT * FROM vegetables RIGHT JOIN fruits ON vegetables.price = fruits.price; +------+---------+-------+----+----------+-------+ | id | name | price | id | name | price | +------+---------+-------+----+----------+-------+ | 3 | kartofi | 3 | 1 | banani | 3 | | NULL | NULL | NULL | 2 | iagodi | 2.5 | | NULL | NULL | NULL | 3 | chereshi | 7 | +------+---------+-------+----+----------+-------+ 3 rows in set (0.00 sec)
Е, вече може би се досетихте как можем да постигнем FULL JOIN - просто трябва да обединим резултата от двете заявки и да премахнем дублиращите се редове (т.е. да използваме UNION без ALL):
(SELECT * FROM vegetables LEFT JOIN fruits ON vegetables.price = fruits.price) UNION (SELECT * FROM vegetables RIGHT JOIN fruits ON vegetables.price = fruits.price); +------+------------+-------+------+----------+-------+ | id | name | price | id | name | price | +------+------------+-------+------+----------+-------+ | 1 | krastavici | 4 | NULL | NULL | NULL | | 2 | domati | 5 | NULL | NULL | NULL | | 3 | kartofi | 3 | 1 | banani | 3 | | NULL | NULL | NULL | 2 | iagodi | 2.5 | | NULL | NULL | NULL | 3 | chereshi | 7 | +------+------------+-------+------+----------+-------+ 5 rows in set (0.00 sec)
Редовете са точно 5, а това е именно каквото очаквахме от FULL JOIN. Този метод върши работа в почти всички случай, но за съжаление НЕ покрива 100% стандарта на определението за FULL JOIN. Проблемът е, че UNION премахва всички дублиращи се редове, а FULL JOIN не го прави ако има такива. За да се демонстрира това трябва в една от таблиците да има дублиращи се редове - в този случай по стандарта за FULL JOIN ще трябва този ред да излезе два пъти, а с горната реализация няма да се получи. Eто един пример:
INSERT INTO vegetables (`id`, `name`, `price`) VALUES (NULL, 'krastavici', 4); SELECT * FROM vegetables; +----+------------+-------+ | id | name | price | +----+------------+-------+ | 1 | krastavici | 4 | | 2 | domati | 5 | | 3 | kartofi | 3 | | 4 | krastavici | 4 | +----+------------+-------+ 4 rows in set (0.00 sec) (SELECT vegetables.name, fruits.name FROM vegetables LEFT JOIN fruits ON vegetables.price = fruits.price) UNION (SELECT vegetables.name, fruits.name FROM vegetables RIGHT JOIN fruits ON vegetables.price = fruits.price); +------------+----------+ | name | name | +------------+----------+ | krastavici | NULL | | domati | NULL | | kartofi | banani | | NULL | iagodi | | NULL | chereshi | +------------+----------+ 5 rows in set (0.00 sec)
Виждаме, че в резултата има само едни 'krastavici', а при истински FULL JOIN трябваше да са две. Този проблем няма да се реши и с UNION ALL, защото в този случай ще се появят други дублиращи се редове, които НЕ трябва да присъстват:
(SELECT vegetables.name, fruits.name FROM vegetables LEFT JOIN fruits ON vegetables.price = fruits.price) UNION ALL (SELECT vegetables.name, fruits.name FROM vegetables RIGHT JOIN fruits ON vegetables.price = fruits.price); +------------+----------+ | name | name | +------------+----------+ | krastavici | NULL | | domati | NULL | | kartofi | banani | | krastavici | NULL | | kartofi | banani | | NULL | iagodi | | NULL | chereshi | +------------+----------+ 7 rows in set (0.00 sec)
Тук "krastavici" се появяват два пъти, както трябва да бъде, но реда "kartofi | banani" се появява два пъти, а не трябва да бъде така! Затова за реализация на FULL JOIN се използва друг подход, който е модификация на последния:
(SELECT vegetables.name, fruits.name FROM vegetables LEFT JOIN fruits ON vegetables.price = fruits.price) UNION ALL (SELECT vegetables.name, fruits.name FROM vegetables RIGHT JOIN fruits ON vegetables.price = fruits.price WHERE vegetables.price IS NULL); +------------+----------+ | name | name | +------------+----------+ | krastavici | NULL | | domati | NULL | | kartofi | banani | | krastavici | NULL | | NULL | iagodi | | NULL | chereshi | +------------+----------+ 6 rows in set (0.00 sec)
С добавеното условие WHERE ние премахнахме редовете, които вече са общи за двете таблици. Така вече имаме напълно функционална FULL JOIN заявка!
В по-старите версии на MySQL, където UNION не съществува, реализацията се прави чрез създаването на трета временна таблица. Няма да се спираме на това решение.
А в последния пример в кой случай имаме цена NULL ? Това нещо не ми стана ясно...
RIGHT JOIN е. Добавя тези и плодове, на които не съответстват зеленчуци. С where условието остават само те.