Uppsättningsoperatorer är de SQL-operatorer som sysslar med att kombinera, på olika sätt, olika resultatuppsättningar. Säg att du har två olika SELECT
Om du vill kombinera till en enda resultatuppsättning kommer setoperatorerna in i bilden. MariaDB har stött UNION
och UNION ALL
set operatorer under lång tid, och dessa är de absolut vanligaste SQL set operatorerna.
Men vi går före oss själva här, låt mig först förklara SQL-setoperatorerna som vi har och hur de fungerar. Om du vill prova detta kan du använda din befintliga distribution av MariaDB Server, eller prova detta i en MariaDB SkySQL-molndatabas.
UNION och UNION ALL
UNION
och UNION ALL
setoperatorer lägger till resultatet av två eller flera resultatuppsättningar. Låt oss börja med UNION ALL
och UNION
kommer då att vara en variant av UNION ALL
.
Låt oss ta en titt på hur det ser ut i SQL. Låt oss anta att vi driver en webshop och att vi har ett lager för produkterna vi säljer. Nu vill vi se alla produkter som är beställda eller finns i lager, en fråga om detta skulle se ut ungefär så här:
SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id UNION ALL SELECT i.prod_id, p.prod_name FROM inventory i JOIN products p ON i.prod_id =p.id;
Detta är, i mängdteorin, UNION
av de uppsättningar av produkter som har beställts och de uppsättningar av produkter som finns i lager. Vilket är bra i teorin, men det finns ett problem med resultatet av denna fråga. Problemet är att en produkt som förekommer i både beställningarna och lagret, eller på flera ställen i lagret, kommer att visas mer än en gång i utdata. Det här problemet är anledningen till UNION ALL
används inte mycket och istället UNION DISTINCT
(DISTINCT
är standard och kan ignoreras) används. Till exempel:
SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id UNION SELECT i.prod_id, p.prod_name FROM inventory i JOIN products p ON i.prod_id =p.id;
Med denna fråga listas en produkt som antingen är i beställning eller som finns i lagret endast en gång. Observera att när vi tar bort dubbletter här är det värdena som jämförs, så två rader med samma värden i samma kolumn anses lika, även om värdena kommer från olika tabeller eller kolumner.
För att vara ärlig finns det dock ingenting i frågan ovan som inte kan göras med en vanlig SELECT
från produkterna bord och några sammanfogningar. På vissa sätt en UNION
kan vara lättare att läsa. Å andra sidan, om vi vill ha en lista över produkter på beställning eller i inventeringen, och även vill veta vilken det var, då skulle en fråga vara ungefär så här:
SELECT 'On order', oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id UNION SELECT 'Inventory', i.prod_id, p.prod_name FRÅN inventory i JOIN products p ON i.prod_id =p.id;
Här är en fråga som inte är lätt att göra med en enkel SELECT
från produkterna tabell som vi tittar på samma rad från produkttabellen två gånger (en gång för order_items och en gång för inventeringen ).
Fler SQL-uppsättningsoperatorer
Med MariaDB Server 10.3 kom två nya SQL-uppsättningsoperatörer, till stor del introducerade för att förbättra Oracle-kompatibiliteten, men dessa operatörer är användbara i sig. MariaDB Server 10.4 lägger sedan till möjligheten att styra inställd operatörsprioritet. Vi ska ta en titt på det också. Utan möjligheten att kontrollera operatörens företräde fungerar inte alltid de inställda operatörerna som du vill eller förväntar dig.
De nya SQL-uppsättningsoperatorerna är INTERSECT
och EXCEPT
och de är användbara, särskilt när du använder analyser. Även om JOIN
s och andra konstruktioner kan ofta användas istället, SQL set-operatorer tillåter en SQL-syntax som kan vara lättare att läsa och förstå. Och om du har Oracle-applikationer som du migrerar till MariaDB, är användbarheten av dessa operatörer uppenbar.
IntersECT set-operatören
INTERSECT
operatorn returnerar alla objekt som finns i två eller flera uppsättningar, eller i SQL-termer, alla rader som finns i två resultatuppsättningar. I det här fallet skapas ett tvärsnitt av de två uppsättningarna av objekt. I SQL-termer betyder det att endast rader som finns i båda uppsättningarna returneras, så om jag vill kontrollera vilka produkter jag har på beställning och vilka som också finns i lager kan en fråga se ut så här:
SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id INTERSECT SELECT i.prod_id, p.prod_name FROM inventory i JOIN products p ON i.prod_id =p.id;
Återigen, den här frågan kan konstrueras med en JOIN
på produkterna tabell, men frågan ovan är lite tydligare om vad vi försöker uppnå.
EXCEPT set-operatorn
I fallet med EXCEPT
operatör vill vi ha föremålen som finns i en av uppsättningarna, men inte i den andra. Så, återigen med exemplet ovan, om vi vill se produkterna som vi har på beställning men som vi inte har lager för, kan vi skriva en fråga så här:
SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id UTOM SELECT i.prod_id, p.prod_name FRÅN inventory i JOIN products p ON i.prod_id =p.id;
Återigen, det finns andra sätt att skriva just den här frågan, men för andra, mer avancerade frågor när vi kombinerar data från två olika tabeller är detta inte fallet.
Kombinera flera setoperatorer
Du kan kombinera fler än 2 setoperatorer om detta är användbart. Låt oss till exempel se om vi kan hitta produkter som är beställda och har levererats eller finns i lager. SQL för detta skulle se ut ungefär så här:
SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id INTERSECT SELECT d.prod_id, p.prod_name FRÅN leveranser d JOIN products p ON d.prod_id =p.id UNION SELECT i.prod_id, p.prod_name FRÅN lager i JOIN products p ON i.prod_id =p.id;
För att uttrycka detta i klartext så är det som är på gång att jag först kontrollerar vilka produkter som finns på beställning och som har levererats, och sedan kombinerar jag denna uppsättning produkter med alla produkter i lagret. Alla produkter som inte finns i resultatuppsättningen finns inte i lagret men kan vara på beställning eller kanske ha levererats, men inte båda.
Men nu, låt oss uttrycka detta på ett annat sätt och se vad som händer. Jag vill ha en lista på alla produkter som finns i lager eller har levererats och är beställda. SQL skulle då vara ungefär så här, liknande SQL ovan men något annorlunda:
SELECT i.prod_id, p.prod_name FROM inventory i JOIN products p ON i.prod_id =p.id UNION SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id =p.id INTERSECT SELECT d.prod_id, p.prod_name FRÅN leveranser d JOIN products p ON d.prod_id =p.id;
Hur tolkar du detta då? Listar du produkter som finns i lager och som är beställda och de produkter som levereras? Så här ser det ut, eller hur? Det är bara den där INTERSECT
(och EXCEPT
för den delen) har företräde över UNION
. De två SQL-satserna ger samma resultatuppsättning, åtminstone i MariaDB och det är så SQL-standarden säger att saker och ting ska fungera. Men det finns ett undantag, Oracle.
Hur detta fungerar i Oracle
Oracle har haft alla fyra SQL-uppsättningsoperatorerna (UNION
, UNION ALL
, INTERSECT
och EXCEPT
) under lång tid, långt innan de standardiserades, så deras implementering är lite annorlunda. Låt oss försöka med tabellerna ovan och infoga lite data i dem. Datan är väldigt enkel och speglar ett företag som inte är så framgångsrikt, men det fungerar som ett exempel och vi visar bara de relevanta kolumnerna här.
Tabell | produkter | beställningsartiklar | inventering | leveranser | ||
Kolumn | prod_id | prod_name | order_id | prod_id | prod_id | prod_id |
Data | 1 | Vas blå | 1 | 1 | 1 | 2 |
2 | Vas Röd | 2 | 1 | 2 | 3 | |
3 | Röd matta | 2 | 3 |
Med data på plats, låt oss titta på den sista SQL-satsen ovan igen. Det finns en funktion som låter dig styra prioritet, och det är att använda parenteser, eller parenteser (Introducerad i MariaDB 10.4, se https://jira.mariadb.org/browse/MDEV-11953), och använda dessa för att illustrera vad som pågår, skulle uttalandet se ut så här:
MariaDB> VÄLJ i.prod_id, p.prod_name -> FRÅN inventering i JOIN products p ON i.prod_id =p.id -> UNION -> (SELECT oi.prod_id, p.prod_name -> FROM order_items oi JOIN products p ON oi.prod_id =p.id -> INTERSECT -> SELECT d.prod_id, p.prod_name -> FRÅN leveranser d JOIN products p ON d.prod_id =p.id); +--------+------------+ | prod_id | prod_name | +--------+------------+ | 1 | Vas blå | | 2 | Vas Röd | | 3 | Matta Röd | +--------+------------+ 3 rader i set (0,001 sek)
Låt oss nu använda samma teknik för att tvinga de tre komponenterna i frågan att fungera i strikt ordning:
MariaDB> (VÄLJ i.prod_id, p.prod_name -> FRÅN inventering i JOIN products p ON i.prod_id =p.id -> UNION -> SELECT oi.prod_id, p.prod_name -> FROM order_items oi JOIN products p ON oi.prod_id =p.id) -> INTERSECT -> SELECT d.prod_id, p.prod_name -> FRÅN leveranser d JOIN products p ON d.prod_id =p.id; +--------+------------+ | prod_id | prod_name | +--------+------------+ | 2 | Vas Röd | | 3 | Matta Röd | +--------+------------+ 2 rader i set (0,001 sek)
Och slutligen utan parentes:
MariaDB [test]> VÄLJ i.prod_id, p.prod_name -> FRÅN lager i JOIN products p ON i.prod_id =p.id -> UNION -> SELECT oi.prod_id, p.prod_name -> FROM order_items oi JOIN products p ON oi.prod_id =p.id -> INTERSECT -> SELECT d.prod_id, p.prod_name -> FRÅN leveranser d JOIN products p ON d.prod_id =p.id; +--------+------------+ | prod_id | prod_name | +--------+------------+ | 1 | Vas blå | | 2 | Vas Röd | | 3 | Matta Röd | +--------+------------+ 3 rader i set (0,001 sek)
Vi ser att MariaDB, enligt standarden, antog att INTERSECT
har företräde framför UNION
. Vilket för oss till Oracle. Låt oss prova ovanstående SQL i Oracle med sqlplus:
SQL> SELECT i.prod_id, p.prod_name 2 FROM inventory i JOIN products p ON i.prod_id =p.id 3 UNION 4 SELECT oi.prod_id, p.prod_name 5 FROM order_items oi JOIN products p ON oi.prod_id =p.id 6 INTERSECT 7 VÄLJ d.prod_id, p.prod_name 8 FRÅN leveranser d JOIN products p ON d.prod_id =p.id; PROD_ID PROD_NAME ---------- ------------------------------------ 2 vas röd 3 matta rödVad är det som händer här, frågar du dig? Tja, Oracle följer inte standarden. De olika setoperatorerna behandlas som lika och ingen har företräde framför den andra. Det här är ett problem när du migrerar applikationer från Oracle till MariaDB, och vad värre är, är att denna skillnad är ganska svår att hitta. Inget fel skapas och data returneras och i många fall returneras rätt data. Men i vissa fall, där data är som i vårt exempel ovan, returneras fel data, vilket är ett problem.
Effekt på migrering av data
Så hur hanterar vi detta om vi migrerar en applikation från Oracle till MariaDB? Det finns några alternativ:
- Skriv om programmet så att det antar att data som returneras från en fråga som denna är i linje med SQL Standard och MariaDB.
- Skriv om SQL-satserna med parenteser så att MariaDB returnerar samma data som Oracle
- Eller, och detta är det smartaste sättet, använd MariaDB
SQL_MODE=Oracle
inställning.
För det sista och smartaste sättet att fungera måste vi köra med MariaDB 10.3.7 eller högre (detta föreslogs av yours truly i https://jira.mariadb.org/browse/MDEV-13695). Låt oss kolla hur detta fungerar. Jämför resultatet av denna SELECT
med Oracle en ovan (som ger samma resultat) och den från MariaDB ovanför det (som inte gör det):
MariaDB> set SQL_MODE=Oracle; Fråga OK, 0 rader påverkade (0,001 sek) MariaDB> VÄLJ i.prod_id, p.prod_name -> FRÅN inventering i JOIN products p ON i.prod_id =p.id -> UNION -> SELECT oi.prod_id, p.prod_name -> FROM order_items oi JOIN products p ON oi.prod_id =p.id -> INTERSECT -> SELECT d.prod_id, p.prod_name -> FRÅN leveranser d JOIN products p ON d.prod_id =p.id; +--------+------------+ | prod_id | prod_name | +--------+------------+ | 2 | Vas Röd | | 3 | Matta Röd | +--------+------------+ 2 rader i set (0,002 sek)
Som du kan se, när SQL_MODE
är inställd på Oracle
, MariaDB beter sig verkligen som Oracle. Detta är inte det enda som SQL_MODE=Oracle
gör det så klart, men det är ett av de mindre kända områdena.
Slutsats
Uppsättningsoperatorerna INTERSECT
och EXCEPT
används inte så mycket, även om de förekommer här och där, och det finns vissa användningsområden för dem. Exemplen i den här bloggen är där mer för att illustrera hur dessa operatörer fungerar än för att visa riktigt bra användningsområden för dem. Det finns säkert bättre exempel. Men när du migrerar från Oracle till MariaDB är SQL-uppsättningsoperatorer verkligen användbara eftersom många Oracle-applikationer använder dem, och som man kan se kan MariaDB luras att fungera precis som Oracle, vilket är icke-standard men fortfarande tjänar ett syfte. Men som standard fungerar såklart MariaDB som det ska och följer SQL Standard.
Glad SQL’ing
/Karlsson