Set-operators zijn de SQL-operators die zich bezighouden met het op verschillende manieren combineren van verschillende resultaatsets. Stel dat je twee verschillende SELECT
. hebt s die u wilt combineren tot een enkele resultaatset, komen de set-operators in het spel. MariaDB ondersteunt de UNION
en UNION ALL
set-operators al heel lang, en dit zijn verreweg de meest voorkomende SQL-setoperators.
Maar we lopen hier voorop, laat me eerst uitleggen welke SQL-setoperators we hebben en hoe ze werken. Als u dit eens wilt proberen, kunt u uw bestaande implementatie van MariaDB Server gebruiken of dit uitproberen in een MariaDB SkySQL-clouddatabase.
UNION en UNION ALL
De UNION
en UNION ALL
set-operators voegen het resultaat van twee of meer resultaatsets toe. Laten we beginnen met UNION ALL
en UNION
zal dan een variatie zijn van UNION ALL
.
Laten we eens kijken hoe het eruit ziet in SQL. Laten we aannemen dat we een webshop hebben en dat we voor de producten die we verkopen een inventaris hebben. Nu willen we alle producten zien die in bestelling zijn of in voorraad zijn, een zoekopdracht hiervoor zou er ongeveer zo uitzien:
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;
Dit is, in verzamelingenleer, de UNION
van de sets producten die zijn besteld en de sets producten die in voorraad zijn. Wat in theorie prima is, maar er is een probleem met het resultaat van deze query. Het probleem is dat een product dat zowel in de bestellingen als in de inventaris voorkomt, of op meerdere plaatsen in de inventaris, meer dan eens in de uitvoer zal verschijnen. Dit probleem is de reden waarom UNION ALL
wordt niet veel gebruikt en in plaats daarvan UNION DISTINCT
(DISTINCT
is de standaardwaarde en kan worden genegeerd) wordt gebruikt. Bijvoorbeeld:
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;
Met deze zoekopdracht wordt een product dat in bestelling is of in de inventaris bestaat slechts één keer vermeld. Merk op dat wanneer we hier duplicaten verwijderen, het de waarden zijn die worden vergeleken, dus twee rijen met dezelfde waarden in dezelfde kolom worden als gelijk beschouwd, ook al komen de waarden uit verschillende tabellen of kolommen.
Maar om eerlijk te zijn, er is niets in de bovenstaande zoekopdracht dat niet kan worden gedaan met een gewone SELECT
van de producten tafel en een paar joins. In sommige opzichten een UNION
misschien makkelijker te lezen. Aan de andere kant, als we een lijst met producten in bestelling of in de inventaris willen hebben, en ook willen weten welke het was, dan zou een vraag ongeveer als volgt zijn:
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 FROM inventory i JOIN products p ON i.prod_id = p.id;
Hier is een vraag die niet gemakkelijk te doen is met een simpele SELECT
van de producten tabel aangezien we twee keer naar dezelfde rij van de producttabel kijken (eenmaal voor de order_items en een keer voor de inventaris ).
Meer SQL-set-operators
Met MariaDB Server 10.3 kwamen twee nieuwe SQL-set-operators, grotendeels geïntroduceerd om de Oracle-compatibiliteit te verbeteren, maar deze operators zijn op zichzelf nuttig. MariaDB Server 10.4 voegt vervolgens de mogelijkheid toe om de voorrang van de ingestelde operator te regelen. We zullen daar ook naar kijken. Zonder de mogelijkheid om de voorrang van de operator te bepalen, werken de ingestelde operators niet altijd zoals je zou willen of verwachten.
De nieuwe SQL-setoperators zijn INTERSECT
en EXCEPT
en ze zijn handig, vooral bij het gebruik van analyses. Ook, hoewel JOIN
s en andere constructies kunnen in plaats daarvan vaak worden gebruikt, SQL-setoperators zorgen voor een SQL-syntaxis die gemakkelijker te lezen en te begrijpen is. En als u Oracle-applicaties heeft die u naar MariaDB migreert, is het nut van deze operators duidelijk.
De INTERSECT set-operator
De INTERSECT
operator retourneert alle items die in twee of meer sets voorkomen, of in SQL-termen, alle rijen die in twee resultaatsets voorkomen. In dit geval wordt een dwarsdoorsnede van de twee sets items gemaakt. In SQL-termen betekent dit dat alleen rijen die in beide sets voorkomen worden geretourneerd, dus als ik wil controleren welke producten ik in bestelling heb en welke ook in voorraad zijn, kan een vraag er als volgt uitzien:
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;
Nogmaals, deze query kan worden gemaakt met behulp van een JOIN
op de producten tabel, maar de bovenstaande vraag is iets duidelijker over wat we proberen te bereiken.
De BEHALVE set-operator
In het geval van de EXCEPT
operator, we willen de items die in een van de sets zitten, maar niet in de andere. Dus, nogmaals met behulp van het bovenstaande voorbeeld, als we de producten willen zien die we in bestelling hebben maar waarvoor we geen voorraad hebben, kunnen we een vraag als deze schrijven:
SELECT oi.prod_id, p.prod_name FROM order_items oi JOIN products p ON oi.prod_id = p.id EXCEPT SELECT i.prod_id, p.prod_name FROM inventory i JOIN products p ON i.prod_id = p.id;
Nogmaals, er zijn andere manieren om deze specifieke zoekopdracht te schrijven, maar voor andere, meer geavanceerde zoekopdrachten wanneer we gegevens uit twee verschillende tabellen combineren, is dit niet het geval.
Meerdere set-operators combineren
U kunt meer dan 2 set-operators combineren als dit handig is. Laten we bijvoorbeeld eens kijken of we producten kunnen vinden die in bestelling zijn en al geleverd of op voorraad zijn. De SQL hiervoor ziet er ongeveer zo uit:
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 FROM deliveries d JOIN products p ON d.prod_id = p.id UNION SELECT i.prod_id, p.prod_name FROM inventory i JOIN products p ON i.prod_id = p.id;
Om dit in begrijpelijke taal uit te drukken, wat er aan de hand is, is dat ik eerst kijk welke producten in bestelling zijn en welke zijn geleverd, en dan combineer ik deze set producten met alle producten in de inventaris. Elk product dat niet in de resultatenset staat, is niet in de voorraad maar is mogelijk in bestelling of is mogelijk afgeleverd, maar niet beide.
Maar laten we dit nu anders uitdrukken en kijken wat er gebeurt. Ik wil een lijst van alle producten die op voorraad zijn of geleverd zijn en in bestelling zijn. De SQL zou dan ongeveer zo zijn, vergelijkbaar met de bovenstaande SQL, maar iets anders:
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 FROM deliveries d JOIN products p ON d.prod_id = p.id;
Hoe interpreteer je dit dan? Vermeldt u producten die op voorraad zijn en die in bestelling zijn en de producten die geleverd worden? Dit is hoe dit eruit ziet, toch? Het is gewoon dat INTERSECT
(en EXCEPT
wat dat betreft) heeft voorrang over UNION
. De twee SQL-instructies produceren dezelfde resultatenset, althans in MariaDB en dit is hoe de SQL-standaard zegt dat dingen zouden moeten werken. Maar er is een uitzondering, Oracle.
Hoe dit werkt in Oracle
Oracle heeft alle vier de SQL-setoperators gehad (UNION
, UNION ALL
, INTERSECT
en EXCEPT
) voor een lange tijd, lang voordat ze gestandaardiseerd waren, dus hun implementatie is een beetje anders. Laten we proberen met de bovenstaande tabellen en er wat gegevens in invoegen. De gegevens zijn heel eenvoudig en weerspiegelen een niet zo succesvol bedrijf, maar het werkt als een voorbeeld en we tonen hier alleen de relevante kolommen.
Tabel | producten | order_items | inventaris | leveringen | ||
Kolom | prod_id | prod_name | order_id | prod_id | prod_id | prod_id |
Gegevens | 1 | Vaas Blauw | 1 | 1 | 1 | 2 |
2 | Vaas Rood | 2 | 1 | 2 | 3 | |
3 | Tapijt Rood | 2 | 3 |
Laten we, terwijl de gegevens aanwezig zijn, nog eens naar die laatste SQL-instructie hierboven kijken. Er is een functie waarmee u de prioriteit kunt bepalen, en dat is om haakjes of haakjes te gebruiken (geïntroduceerd in MariaDB 10.4, zie https://jira.mariadb.org/browse/MDEV-11953) en deze te gebruiken om te illustreren wat er aan de hand is, ziet de verklaring er als volgt uit:
MariaDB> 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 -> FROM deliveries d JOIN products p ON d.prod_id = p.id); +---------+------------+ | prod_id | prod_name | +---------+------------+ | 1 | Vase Blue | | 2 | Vase Red | | 3 | Carpet Red | +---------+------------+ 3 rows in set (0.001 sec)
Laten we nu dezelfde techniek gebruiken om de drie componenten van de query af te dwingen om in strikte volgorde te werken:
MariaDB> (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 -> FROM deliveries d JOIN products p ON d.prod_id = p.id; +---------+------------+ | prod_id | prod_name | +---------+------------+ | 2 | Vase Red | | 3 | Carpet Red | +---------+------------+ 2 rows in set (0.001 sec)
En tot slot zonder haakjes:
MariaDB [test]> 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 -> FROM deliveries d JOIN products p ON d.prod_id = p.id; +---------+------------+ | prod_id | prod_name | +---------+------------+ | 1 | Vase Blue | | 2 | Vase Red | | 3 | Carpet Red | +---------+------------+ 3 rows in set (0.001 sec)
We zien dat MariaDB, volgens de standaard, ervan uitging dat INTERSECT
heeft voorrang op UNION
. Dat brengt ons bij Oracle. Laten we de bovenstaande SQL in Oracle proberen met 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 SELECT d.prod_id, p.prod_name 8 FROM deliveries d JOIN products p ON d.prod_id = p.id; PROD_ID PROD_NAME ---------- ------------------------------ 2 Vase Red 3 Carpet Red
Wat is hier aan de hand, vraag je? Welnu, Oracle volgt de standaard niet. De verschillende set-operators worden als gelijk behandeld en geen heeft voorrang op de andere. Dit is een probleem wanneer u applicaties van Oracle naar MariaDB migreert, en wat nog erger is, is dat dit verschil nogal moeilijk te vinden is. Er worden geen fouten gemaakt en gegevens worden geretourneerd en in veel gevallen worden de juiste gegevens geretourneerd. Maar in sommige gevallen, waar de gegevens zijn zoals in ons voorbeeld hierboven, worden de verkeerde gegevens geretourneerd, wat een probleem is.
Effect op het migreren van gegevens
Dus hoe gaan we hiermee om als we een applicatie migreren van Oracle naar MariaDB? Er zijn een paar opties:
- Herschrijf de toepassing zodat deze ervan uitgaat dat de gegevens die worden geretourneerd door een query als deze in overeenstemming zijn met de SQL-standaard en MariaDB.
- Herschrijf de SQL-instructies, gebruik haakjes, zodat MariaDB dezelfde gegevens retourneert als Oracle
- Of, en dit is de slimste manier, gebruik de MariaDB
SQL_MODE=Oracle
instelling.
Voor de laatste en slimste manier om te werken, moeten we MariaDB 10.3.7 of hoger gebruiken (dit werd door ondergetekende gesuggereerd in https://jira.mariadb.org/browse/MDEV-13695). Laten we eens kijken hoe dit werkt. Vergelijk het resultaat van deze SELECT
met de Oracle hierboven (die hetzelfde resultaat oplevert) en die van MariaDB daarboven (die niet):
MariaDB> set SQL_MODE=Oracle; Query OK, 0 rows affected (0.001 sec) MariaDB> 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 -> FROM deliveries d JOIN products p ON d.prod_id = p.id; +---------+------------+ | prod_id | prod_name | +---------+------------+ | 2 | Vase Red | | 3 | Carpet Red | +---------+------------+ 2 rows in set (0.002 sec)
Zoals je kunt zien, wanneer SQL_MODE
is ingesteld op Oracle
, gedraagt MariaDB zich echt als Oracle. Dit is niet het enige dat SQL_MODE=Oracle
natuurlijk wel, maar het is een van de minder bekende gebieden.
Conclusie
De set-operators INTERSECT
en EXCEPT
worden niet zo veel gebruikt, hoewel ze hier en daar wel verschijnen, en er zijn enkele toepassingen voor hen. De voorbeelden in deze blog zijn er meer om te illustreren hoe deze operators werken dan om ze echt goed te gebruiken. Er zijn vast betere voorbeelden. Wanneer u echter van Oracle naar MariaDB migreert, zijn SQL-setoperators erg handig omdat veel Oracle-applicaties ze gebruiken, en zoals te zien is, kan MariaDB worden misleid om net als Oracle te werken, wat niet-standaard is maar nog steeds een doel dient. Maar standaard werkt MariaDB natuurlijk zoals het hoort en volgt het de SQL-standaard.
Veel plezier met SQL
/Karlsson