C, PHP, VB, .NET

Дневникът на Филип Петров


* 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 не съществува, реализацията се прави чрез създаването на трета временна таблица. Няма да се спираме на това решение.

 



2 коментара


  1. Radoslav каза:

    А в последния пример в кой случай имаме цена NULL ? Това нещо не ми стана ясно…

  2. RIGHT JOIN е. Добавя тези и плодове, на които не съответстват зеленчуци. С where условието остават само те.

Добави коментар

Адресът на електронната поща няма да се публикува


*