Code is there to explain the comments to the computer.
—Andy Harris
De WHERE clause in detail
In een SELECT
statement zoals:
SELECT opocode, coordinator
FROM opleidingsonderdeel
WHERE coordinator = 'u0012047'
wordt eerst de FROM
clause uitgevoerd door de databankserver.
Heel de tabel ‘opleidingsonderdeel’ wordt in het werkgeheugen ingeladen. Ik
weet het: we vallen in herhaling, maar dit is echt essentieel om query’s te
kunnen schrijven.
Daarna wordt de WHERE
clause bekeken (indien aanwezig). Alleen
de rijen die voldoen aan de voorwaarde in de WHERE
blijven
bewaard. Alle andere rijen verdwijnen uit het geheugen. Pas daarna komt de
SELECT
. In dit stukje bekijken we meer specifiek een aantal
mogelijkheden van de WHERE
.
Je kan de codevoorbeelden testen in de tabel ‘opleidingsonderdeel’ van het schema ‘uclloket’ (of in je eigen versie van deze tabel in je persoonlijk schema).
Standaard vergelijkingsoperatoren
SQL voorziet de standaard vergelijkingsoperatoren: =
(gelijk aan),
<>
of !=
(niet gelijk aan), >
(groter dan), <
(kleiner dan), >=
(groter
dan of gelijk aan) en <=
(kleiner dan of gelijk aan). Je kan
getallen, strings en datum of tijd vergelijken. Volgende query toont alle OPO’s
die een startdatum voor 5 april 2017 hebben:
SELECT *
FROM opleidingsonderdeel
WHERE van < '2017-04-05' -- datum in ISO-formaat met enkele aanhalingstekens invoeren
Een datum die meer in het verleden ligt is kleiner dan een latere datum, zoals je logisch mag verwachten.
Bij het vergelijken van strings komt er iets meer kijken. Jammer genoeg is het resultaat databasespecifiek. Dat 'a' < 'b' is evident, maar hoe zit het met de vergelijking van 'a' en 'A'? Weet je wat, we proberen het gewoon uit in PostgreSQL:
SELECT 'a' < 'A'
Het resultaat is true
. We besluiten: in PostgreSQL komen
kleine letters voor de hoofdletters. In een ander DBMS is het mogelijk net
omgekeerd.
En wat met een vergelijking tussen 'aap' en 'appel' en tussen 'appel' en 'appelmoes'? Even testen:
SELECT 'aap' > 'appel';
SELECT 'appel' > 'appelmoes'
Het resultaat is twee keer false
. De vergelijking tussen twee
strings gebeurt namelijk letter per letter van links naar rechts.
Bij 'aap' en 'appel' zijn de eerste letters gelijk, dus wordt de tweede
letter bekeken. De tweede ‘a’ van 'aap' is kleiner dan de ‘p’ van 'appel'
en dus is 'aap' < 'appel'
.
Het woord 'appel' is kleiner dan 'appelmoes'. De letters worden één voor één bekeken. Na vijf overeenkomende letters is er geen zesde letter meer in de string 'appel' en dus is dit woord kleiner (korter) dan het langere woord 'appelmoes'.
IS (NOT) NULL
De waarde NULL
in een veld betekent ‘onbekend’. Je kan vanzelfsprekend
ook testen op deze speciale waarde. Je moet er alleen aan denken dat je niet
=
gebruikt maar wel IS
. Volgende query toont een
lijst van alle OPO’s waarvan nog niet bekend is wanneer die gaan stoppen:
SELECT *
FROM opleidingsonderdeel
WHERE tot IS NULL -- dus niet = NULL
Omgekeerd geeft de volgende query de lijst van alle OPO’s die wel een einddatum hebben (en die dus niet meer gegeven worden of waarvan nu al geweten is tot wanneer deze OPO’s in het curriculum zullen staan):
SELECT *
FROM opleidingsonderdeel
WHERE tot IS NOT NULL
Stringpatronen met LIKE
Soms wil je niet zoeken op de volledige waarde van een string, maar
slechts op een deel. Stel dat je een lijst wilt van alle OPO’s die het
woord ‘web’ ergens in de naam hebben (zowel met grote of kleine letter).
We weten al uit een vorig stuk dat we de functie lower()
zeker nuttig zullen gebruiken in deze
query om alle hoofdletters naar kleine letters om te zetten. Maar hoe testen
we op ‘web’ ergens in de naam?
Dat kan op verschillend manieren (o.a. met reguliere expressies). We bekijken hier de eenvoudigste manier m.b.v. LIKE
en een patroon. In dat patroon kan je naast gewone letters ook twee speciale
tekens gebruiken:
%
: 0 of meerdere karakters;_
(underscore): juist 1 karakter.
De volgende query zoekt alle namen die ergens in de naam de substring ‘web’ bevatten (hoofdletters/kleine letters maken niet uit):
SELECT opocode, naam, semester
FROM opleidingsonderdeel
WHERE lower(naam) LIKE '%web%'
Maak nu de volgende oefeningen.
Omschrijf in woorden wat volgende LIKE strings zoeken:
... LIKE 'B%'
... LIKE '_e%'
... LIKE '%e%e%e%'
... LIKE '__a_b%'
... LIKE 'B%' --alle strings die beginnen met hoofdletter B
... LIKE '_e%' --alle strings die als tweede letter een e hebben
... LIKE '%e%e%e%' --alle strings die minstens drie keer een e hebben
... LIKE '__a_b%' --alle strings met een a als derde en een b als vijfde letter
Geef de SQL query waarmee je een lijst genereert van alle lectoren waarvan de lectorcode eindigt op het cijfer 4 met de naam van het OPO waarvoor ze verantwoordelijk zijn.
SELECT coordinator, naam
FROM opleidingsonderdeel
WHERE coordinator like '%4'
-- je had eventueel ook substring() kunnen gebruiken. Probeer je eens op die manier?
Rijen behouden die NIET aan een voorwaarde voldoen
Met een voorwaarde (conditie) in de WHERE
clause houden we alleen
die rijen over die voldoen aan die voorwaarde. Wat als je nu net alle andere
rijen wilt hebben, dus die niet voldoen aan de voorwaarde? Hiervoor
bestaat het keyword NOT
. Volgende query geeft alle OPO’s die
niet voldoen aan de voorwaarde, dus die niet beginnen met de letters
‘Web’:
SELECT opocode, naam, semester
FROM opleidingsonderdeel
WHERE NOT naam LIKE 'Web%'
Geef een overzicht van alle OPO’s (opocode, naam en aantal studiepunten) waarvan de naam geen enkele ‘a’ (enkel kleine letter, hoofdletter ‘A’ mag wel voorkomen) bevat. Schrijf de query. Wat pas je aan in je oplossing als ook de hoofdletter ‘A’ niet mag voorkomen?
SELECT opocode, naam, studiepunten
FROM opleidingsonderdeel
WHERE NOT naam like '%a%' -- aanpassing: NOT lower(naam) like '%a%'
Condities koppelen met AND en OR
We kunnen meerdere voorwaarden koppelen met AND
en OR
, trouwens ook in combinatie met NOT
te gebruiken:
-
A AND B
: enkelTRUE
als zowel A als BTRUE
zijn; -
A OR B
:TRUE
als één van beide A of B of allebeiTRUE
zijn.
Wat doet volgende query:
SELECT *
FROM opleidingsonderdeel
WHERE taal = 'nl' AND lower(naam) LIKE '%prog%'
Schrijf de query die de lijst genereert van alle OPO’s die in het Nederlands of het Frans gegeven worden.
SELECT *
FROM opleidingsonderdeel
WHERE taal = 'nl' OR taal = 'fr' -- we zien later een kortere manier om dit te noteren
Je kan meer dan twee voorwaarden koppelen. Je moet echter wel oppassen zoals volgend voorbeeld aantoont. Geef een lijst van alle OPO’s die in het Nederlands of het Frans gegeven worden en die enkel in het nieuwe curriculum zullen zitten. We proberen volgende query:
SELECT *
FROM opleidingsonderdeel
WHERE taal = 'nl' OR taal = 'fr' AND tot IS NULL
Deze query geeft het foute resultaat. Voer de query uit en leg uit waarom dit resultaat niet klopt.
De reden heeft te maken met de volgorde van de bewerkingen. Net zoals
je lang geleden leerde dat 3 + 4 · 2 moet gelezen worden als 3 + (4 ·
2) omdat de vermenigvuldiging voorrang heeft op de optelling, net zo
is er een volgorde in operatoren in een WHERE
.
De volgorde van de operatoren is, van hoge prioriteit naar lage:
-
eerst vergelijkingsoperatoren zoals
=
,<
,>
,<=
, …, - dan
NOT
, - vervolgens
AND
en - tenslotte als allerlaatste
OR
.
Dat betekent dat bovenstaande conditie niet gewoon van links naar rechts
uitgevoerd wordt, maar als taal = 'nl' OR (taal = 'fr' AND tot IS NULL)
. Deze voorwaarde kan je vertalen als “alle Nederlandstalige OPO’s (van
heden of verleden, maakt niet uit) ofwel alle Franstalige die in het
huidige curriculum zitten”. Dat is niet wat we wilden.
De oplossing is: gebruik haakjes. De query zoals we hem bedoelden wordt dus:
SELECT *
FROM opleidingsonderdeel
WHERE (taal = 'nl' OR taal = 'fr') AND tot IS NULL
Bij complexe samengestelde voorwaarden zijn haakjes altijd een goed idee omdat ze de leesbaarheid van je conditie verhogen.
BETWEEN
We beginnen met een eenvoudige oefening:
Geef alle OPO’s (code, naam en SP) met 3 tot en met 8 SP.
SELECT opocode, naam, studiepunten
FROM opleidingsonderdeel
WHERE studiepunten >=3 AND studiepunten <=8
SQL biedt een wat kortere notatie voor dit soort query’s die beter aansluit bij het gewone taalgebruik:
SELECT opocode, naam, studiepunten
FROM opleidingsonderdeel
WHERE studiepunten BETWEEN 3 AND 8 -- beide grenzen zijn inbegrepen
Als je op een examen zelf query’s moet schrijven, mag je altijd kiezen of
je voor de BETWEEN
notatie of de iets langere versie gaat. Je
moet beide notaties natuurlijk wel snappen.
IN
Met het keyword IN kan je een voorwaarde korter noteren. Ook hier weer een kleine oefening om te beginnen:
Stel de query op die alle OPO’s toont waarvan de coördinator één van volgende personen is: u0012047, u0015584, u0024689 en u0031447.
SELECT opocode, naam, coordinator
FROM opleidingsonderdeel
WHERE coordinator = 'u0012047' OR coordinator = 'u0015584' OR
coordinator = 'u0024689' OR coordinator = 'u0031447'
Beetje omslachtig, niet? Dat kan dus korter met IN
:
SELECT opocode, naam, coordinator
FROM opleidingsonderdeel
WHERE coordinator IN ('u0012047','u0015584','u0024689','u0031447')
Overzicht operatoren
Hieronder vind je een overzicht van de meest gebruikte operatoren in een
WHERE
clause.
Operator | Functie | Voorbeeld |
---|---|---|
= | Gelijk aan | WHERE naam = 'Jan Van Hee' |
<> of != | Niet gelijk aan | WHERE academiejaar <> 2023 |
> | Groter dan | WHERE bedrag > 250 |
< | Kleiner dan | WHERE bedrag < 400 |
>= | Groter dan of gelijk aan | WHERE bedrag >= 250 |
<= | Kleiner dan of gelijk aan | WHERE bedrag <= 400 |
BETWEEN | Tussen twee waarden | WHERE bedrag BETWEEN 250 AND 400 |
IN | Komt overeen met een waarde | WHERE stad IN ('Leuven', 'Brussel', 'Gent') |
LIKE | Volgt een patroon (met '%' of '_', hoofdlettergevoelig) | WHERE naam LIKE 'Jan%' |
ILIKE | Volgt een patroon (niet hoofdlettergevoelig) | WHERE naam ILIKE 'jan%' |
NOT | Negeert een voorwaarde | WHERE naam NOT LIKE 'Jan%' |
Oefeningen SQLzoo
SQLzoo is een interactief oefenplatform. Je voert SQL-code in die het systeem voor jou nakijkt. Het is een goed idee om deze oefeningen te maken. Je leert immers werken met andere tabellen dan die die we in de cursustekst doorlopend gebruiken.
Tabel ‘world’
Deze tabel bevat volgende gegevens van landen:
- naam
- continent
- oppervlakte
- aantal inwoners
- bruto binnenlands product
Maak als kennismaking met deze tabel oefeningenreeks 1, met drie kleine oefeningen. Je gebruikt hier WHERE
, IN
en BETWEEN
(en vanzelfsprekend SELECT
en FROM
).
Maak quiz 1 met 7 vragen
met een focus op WHERE
.
Oefenreeks 2 op de ‘world’ tabel bevat 13 oefeningen in de stijl van de vorige, maar
met extra rekenfuncties zoals ROUND
en stringmanipulatie.
Quiz 2 bevat opnieuw 7 vragen. Let op de details!