Операторите за набор са SQL операторите, които се занимават с комбиниране по различни начини на различни набори от резултати. Да речем, че имате два различни SELECT
s, които искате да комбинирате в един набор от резултати, операторите за набор влизат в игра. MariaDB поддържа UNION
и UNION ALL
set оператори за дълго време и това са най-често срещаните SQL оператори за набор.
Но тук се изпреварваме, нека първо да обясня операторите на SQL набор, които имаме и как работят. Ако искате да опитате това, можете да използвате съществуващото си внедряване на MariaDB Server или да изпробвате това в облачна база данни на MariaDB SkySQL.
UNION и UNION ALL
UNION
и UNION ALL
операторите за набор добавят резултата от два или повече набора от резултати. Нека започнем с UNION ALL
и UNION
тогава ще бъде вариант на UNION ALL
.
Нека да разгледаме как изглежда в SQL. Да предположим, че управляваме уеб магазин и че за продуктите, които продаваме, имаме инвентар. Сега искаме да видим всички продукти, които са по поръчка или са в инвентара, заявка за това ще изглежда така:
ИЗБЕРЕТЕ oi.prod_id, p.prod_name ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id UNION ВСИЧКИ ИЗБЕРЕТЕ i.prod_id, p.prod_name ОТ инвентара i ПРИСЪЕДИНЕТЕ продукти p ON i.prod_id =p.id;
В теорията на множеството това е UNION
на комплектите продукти, които са поръчани и комплектите продукти, които са в инвентара. Което е добре на теория, но има проблем с резултата от тази заявка. Проблемът е, че продукт, който се появява както в поръчките, така и в инвентара, или на множество места в инвентара, ще се появи повече от веднъж в изхода. Този проблем е причината UNION ALL
не се използва много и вместо това UNION DISTINCT
(DISTINCT
е по подразбиране и може да се игнорира). Например:
ИЗБЕРЕТЕ oi.prod_id, p.prod_name ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id UNION ИЗБЕРЕТЕ i.prod_id, p.prod_name ОТ инвентара i ПРИСЪЕДИНЕТЕ продукти p ON i.prod_id =p.id;предварително>С тази заявка продукт, който е по поръчка или съществува в инвентара, е посочен само веднъж. Имайте предвид, че когато премахваме дубликати тук, стойностите се сравняват, така че два реда с еднакви стойности в една и съща колона се считат за равни, въпреки че стойностите идват от различни таблици или колони.
За да бъда честен обаче, няма нищо в заявката по-горе, което да не може да се направи с обикновен
SELECT
от продуктите маса и няколко съединения. В известен смисълUNION
може да е по-лесно за четене. От друга страна, ако искаме да имаме списък с продукти по поръчка или в инвентара и също така искаме да знаем кой е бил, тогава заявката ще бъде нещо подобно:ИЗБЕРЕТЕ 'По поръчка', oi.prod_id, p.prod_name FROM order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id UNION ИЗБЕРЕТЕ 'Inventory', i.prod_id, p.prod_name ОТ инвентар i ПРИСЪЕДИНЕТЕ продукти p ON i.prod_id =p.id;Ето заявка, която не е лесна за изпълнение с обикновен
SELECT
от продуктите таблица, тъй като разглеждаме един и същ ред от таблицата с продукти два пъти (веднъж за order_items и веднъж за инвентара ).Още оператори за SQL набор
С MariaDB Server 10.3 дойдоха два нови оператора за SQL набор, въведени до голяма степен за подобряване на съвместимостта с Oracle, но тези оператори са полезни сами по себе си. След това MariaDB Server 10.4 добавя възможността за контрол на приоритета на оператора. Ще разгледаме и това. Без възможност за контрол на приоритета на оператора, зададените оператори не винаги работят, както бихте искали или очаквате.
Новите оператори за SQL набор са
INTERSECT
иEXCEPT
и те са полезни, особено при използване на анализи. Също така, въпреки чеJOIN
s и други конструкции често могат да се използват вместо това, SQL операторите за набор позволяват SQL синтаксис, който може да бъде по-лесен за четене и разбиране. И ако имате приложения на Oracle, които мигрирате към MariaDB, полезността на тези оператори е очевидна.
Операторът за набор INTERSECT
INTERSECT
операторът ще върне всички елементи, които съществуват в два или повече набора, или в SQL термини, всички редове, които съществуват в два набора от резултати. В този случай се създава напречно сечение на двата комплекта елементи. В термините на SQL това означава, че се връщат само редове, които съществуват и в двата набора, така че ако искам да проверя кои продукти имам по поръчка и кои също са в инвентара, една заявка може да изглежда така:ИЗБЕРЕТЕ oi.prod_id, p.prod_name ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id INTERSECT SELECT i.prod_id, p.prod_name ОТ инвентара i ПРИСЪЕДИНЕТЕ се продукти p ON i.prod_id =p.id;предварително>Отново тази заявка може да бъде конструирана с помощта на
JOIN
на продуктите таблица, но заявката по-горе е малко по-ясна за това, което се опитваме да постигнем.Операторът за набор EXCEPT
В случай на
EXCEPT
оператор, искаме елементите, които са в един от наборите, но не и в другия. Така че, отново използвайки примера по-горе, ако искаме да видим продуктите, които имаме по поръчка, но за които нямаме инвентар, можем да напишем заявка като тази:ИЗБЕРЕТЕ oi.prod_id, p.prod_name ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id ОСВЕН ИЗБЕРЕТЕ i.prod_id, p.prod_name ОТ инвентара i ПРИСЪЕДИНЕТЕ се продукти p ON i.prod_id =p.id;предварително>Отново има други начини за писане на тази конкретна заявка, но за други, по-разширени заявки, когато комбинираме данни от две различни таблици, това не е така.
Комбиниране на множество оператори за набор
Можете да комбинирате повече от 2 набора оператора, ако това е полезно. Например, нека видим дали можем да намерим продукти, които са по поръчка и са доставени или са на склад. SQL за това би изглеждал така:
ИЗБЕРЕТЕ oi.prod_id, p.prod_name ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id INTERSECT SELECT d.prod_id, p.prod_name ОТ доставки d ПРИСЪЕДИНЕТЕ продукти p ON d.prod_id =p.id UNION SELECT i.prod_id, p.prod_name ОТ инвентара i ПРИСЪЕДИНЕТЕ се към продукти p ON i.prod_id =p.id;За да изразя това на разбираем език, това, което се случва е, че първо проверявам кои продукти са поръчани и кои са доставени, а след това комбинирам този набор от продукти с всички продукти в инвентара. Всеки продукт, който не е в набора от резултати, не е в инвентара но може да е по поръчка или да е доставено, но не и двете.
Но сега нека изразим това по различен начин и да видим какво ще се случи. Искам списък на всички продукти, които са на склад или са доставени и са по поръчка. Тогава SQL ще бъде нещо подобно, подобно на SQL по-горе, но малко по-различно:
ИЗБЕРЕТЕ i.prod_id, p.prod_name ОТ инвентара i JOIN продукти p ON i.prod_id =p.id UNION SELECT oi.prod_id, p.prod_name ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id INTERSECT SELECT SELECT SELECT. d.prod_id, p.prod_name ОТ доставки d ПРИСЪЕДИНЕТЕ се към продукти p ON d.prod_id =p.id;Как тълкувате това тогава? Изброявате ли продукти, които са на склад и които са по поръчка и продуктите, които се доставят? Ето как изглежда това, нали? Просто това е
INTERSECT
(иEXCEPT
по този въпрос) има приоритет презUNION
. Двата SQL оператора произвеждат един и същ набор от резултати, поне в MariaDB и ето как SQL стандартът казва, че нещата трябва да работят. Но има изключение, Oracle.Как работи това в Oracle
Oracle има и четирите оператора на набора SQL (
UNION
,UNION ALL
,INTERSECT
иEXCEPT
) за дълго време, много преди да бъдат стандартизирани, така че изпълнението им е малко по-различно. Нека опитаме с горните таблици и да вмъкнем някои данни в тях. Данните са много прости и отразяват не толкова успешна компания, но работят като пример и тук показваме само съответните колони.
Таблица | продукти | поръчкови_артикули | инвентар | доставки | ||
Колона | prod_id | име на продукта | идентификатор_на_поръчка | prod_id | prod_id | prod_id |
Данни | 1 | Синя ваза | 1 | 1 | 1 | 2 |
2 | Червена ваза | 2 | 1 | 2 | 3 | |
3 | Червен килим | 2 | 3 |
С наличните данни, нека отново да разгледаме последния SQL израз по-горе. Има функция, която ви позволява да контролирате приоритета и това е да използвате скоби или скоби (въведено в MariaDB 10.4, вижте https://jira.mariadb.org/browse/MDEV-11953) и използването им за илюстриране какво се случва, изявлението ще изглежда така:
MariaDB> ИЗБЕРЕТЕ i.prod_id, p.prod_name -> ОТ инвентара i ПРИСЪЕДИНЯВАЙТЕ се към продуктите p ON i.prod_id =p.id -> UNION -> (ИЗБЕРЕТЕ oi.prod_id, p.prod_name -> ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id -> INTERSECT -> SELECT d.prod_id, p.prod_name -> ОТ доставки d ПРИСЪЕДИНЕТЕ продукти p ON d.prod_id =p.id); +---------+-----------+ | prod_id | prod_name | +---------+-----------+ | 1 | Синя ваза | | 2 | Червена ваза | | 3 | Червен килим | +---------+-----------+ 3 реда в комплект (0,001 сек)
Сега нека използваме същата техника, за да наложим трите компонента на заявката да работят в строг ред:
MariaDB> (ИЗБЕРЕТЕ i.prod_id, p.prod_name -> ОТ инвентара i ПРИСЪЕДИНЕТЕ се към продуктите p ON i.prod_id =p.id -> UNION -> ИЗБЕРЕТЕ oi.prod_id, p.prod_name -> ОТ order_items oi ПРИСЪЕДИНЕТЕ се към продукти p ON oi.prod_id =p.id) -> INTERSECT -> SELECT d.prod_id, p.prod_name -> ОТ доставки d ПРИСЪЕДИНЕТЕ продукти p ON d.prod_id =p.id; +---------+-----------+ | prod_id | prod_name | +---------+-----------+ | 2 | Червена ваза | | 3 | Червен килим | +---------+-----------+ 2 реда в комплект (0,001 сек)
И накрая без скоби:
MariaDB [тест]> ИЗБЕРЕТЕ i.prod_id, p.prod_name -> ОТ инвентара i ПРИСЪЕДИНЯВАЙТЕ се към продукти p ON i.prod_id =p.id -> UNION -> SELECT oi.prod_id, p.prod_name -> ОТ order_items oi ПРИСЪЕДИНЕТЕ продуктите p ON oi.prod_id =p.id -> INTERSECT -> SELECT d.prod_id, p.prod_name -> ОТ доставки d ПРИСЪЕДИНЕТЕ продукти p ON d.prod_id =p.id; +---------+-----------+ | prod_id | prod_name | +---------+-----------+ | 1 | Синя ваза | | 2 | Червена ваза | | 3 | Червен килим | +---------+-----------+ 3 реда в комплект (0,001 сек)
Виждаме, че MariaDB, следвайки стандарта, прие, че INTERSECT
има предимство пред UNION
. Което ни отвежда до Oracle. Нека опитаме горния SQL в Oracle с помощта на sqlplus:
SQL> ИЗБЕРЕТЕ i.prod_id, p.prod_name 2 ОТ инвентара i ПРИСЪЕДИНЕТЕ продукти p ON i.prod_id =p.id 3 UNION 4 SELECT oi.prod_id, p.prod_name 5 ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id 6 INTERSECT 7 SELECT d.prod_id, p.prod_name 8 ОТ доставки d ПРИСЪЕДИНЕТЕ продукти p ON d.prod_id =p.id; PROD_ID PROD_NAME ---------- ------------------------------ 2 ваза Червена 3 Червен килимпредварително>Какво става тук, питате? Е, Oracle не следва стандарта. Различните оператори на набора се третират като равни и никой няма предимство пред другия. Това е проблем, когато мигрирате приложения от Oracle към MariaDB и което е по-лошо е, че тази разлика е доста трудна за намиране. Не се създава грешка и се връщат данни и в много случаи се връщат правилните данни. Но в някои случаи, когато данните са като в нашия пример по-горе, се връщат грешни данни, което е проблем.
Ефект върху мигрирането на данни
И така, как да се справим с това, ако мигрираме приложение от Oracle към MariaDB? Има няколко опции:
- Пренапишете приложението, така че да приеме, че данните, върнати от заявка като тази, са в съответствие със стандарта на SQL и MariaDB.
- Пренапишете SQL изразите, като използвате скоби, така че MariaDB да връща същите данни като Oracle
- Или и това е най-умният начин, използвайте MariaDB
SQL_MODE=Oracle
настройка.
За последния и най-интелигентен начин да работим, трябва да работим с MariaDB 10.3.7 или по-нова версия (това беше предложено от вас наистина в https://jira.mariadb.org/browse/MDEV-13695). Нека проверим как работи това. Сравнете резултата от този SELECT
с Oracle по-горе (който дава същия резултат) и този от MariaDB по-горе (който не):
MariaDB> задайте SQL_MODE=Oracle; Заявка ОК, 0 засегнати реда (0,001 сек) MariaDB> ИЗБЕРЕТЕ i.prod_id, p.prod_name -> ОТ инвентара i ПРИСЪЕДИНЕТЕ продукти p ON i.prod_id =p.id -> UNION -> SELECT oi.prod_id, p.prod_name -> ОТ order_items oi ПРИСЪЕДИНЕТЕ продукти p ON oi.prod_id =p.id -> INTERSECT -> SELECT d.prod_id, p.prod_name -> ОТ доставки d ПРИСЪЕДИНЕТЕ продукти p ON d.prod_id =p.id; +---------+-----------+ | prod_id | prod_name | +---------+-----------+ | 2 | Червена ваза | | 3 | Червен килим | +---------+-----------+ 2 реда в комплект (0,002 сек)
Както можете да видите, когато SQL_MODE
е настроен на Oracle
, MariaDB наистина се държи като Oracle. Това не е единственото нещо, което SQL_MODE=Oracle
разбира се, но това е една от по-малко известните области.
Заключение
Операторите за множество INTERSECT
и EXCEPT
не се използват толкова много, въпреки че се появяват тук-там и има някои приложения за тях. Примерите в този блог са повече, за да илюстрират как работят тези оператори, отколкото да покажат наистина добра употреба за тях. Сигурно има по-добри примери. Въпреки това, когато мигрирате от Oracle към MariaDB, операторите за набор от SQL са наистина полезни, тъй като много приложения на Oracle ги използват и както се вижда, MariaDB може да бъде подведена да работи точно като Oracle, който не е стандартен, но все пак служи за цел. Но по подразбиране, разбира се, MariaDB работи както трябва и следва SQL стандарта.
Приятно изпълнение на SQL
/Karlsson