Skip to content

6. Verdieping in de taal SQL

6.1. Introduction

In dit hoofdstuk presenteren we

  • andere syntaxisvormen van het commando SELECT, waardoor dit een zeer krachtig opvraagcommando is, met name om meerdere tabellen tegelijk te raadplegen.
  • uitgebreide syntaxis van reeds besproken commando’s

Om de verschillende commando's te illustreren, werken we met de volgende tabellen die worden gebruikt voor het beheer van bestellingen in een PME-systeem voor de distributie van boeken:

6.1.1. de tabel CLIENTS

Deze tabel bevat informatie over de klanten van de PME:

 

Image

ID
uniek identificatienummer van de klant – primaire sleutel
NOM
naam van de klant
STATUT
I = Particulier, E = Bedrijf, A = Overheid
PRENOM
voornaam in het geval van een particulier
CONTACT
Naam van de contactpersoon bij de klant (in het geval van een bedrijf of overheidsinstantie)
RUE
Adres van de klant - straat
VILLE
plaats
CPOSTAL
postcode
TELEPH
Telefoonnummer
DEPUIS
Sinds wanneer klant?
DEBITEUR
O (Ja) als de klant het bedrijf geld verschuldigd is en N (Nee) anders.

6.1.2. de tabel ARTICLES

Deze tabel bevat informatie over de verkochte producten, in dit geval boeken. De structuur is als volgt:

Image

ISBN
een uniek identificatienummer voor een boek (ISBN = International Standard Book Number) – primaire sleutel
TITRE
Titel van het boek
CODEDITEUR
Code die een uitgever uniek identificeert
AUTEUR
Naam van de auteur
RESUME
Samenvatting van het boek
QTEANCOUR
Verkochte hoeveelheid in het jaar
QTEANPREC
Verkochte hoeveelheid vorig jaar
DERNVENTE
Datum van de laatste verkoop
QTERECUE
Hoeveelheid van de laatste levering
DERNLIV
Datum van de laatste levering
PRIXVENTE
Verkoopprijs
COUT
Aankoopprijs
MINCDE
Minimale bestelhoeveelheid
MINSTOCK
Minimale voorraad
QTESTOCK
Voorraad

De inhoud zou als volgt kunnen zijn:

Image

6.1.3. de tabel COMMANDES

Hierin wordt informatie over de bestellingen van klanten opgeslagen. De structuur is als volgt:

Image

NOCMD
Uniek identificatienummer van een bestelling – primaire sleutel
IDCLI
Klantnummer van degene die deze bestelling plaatst - buitenlandse sleutel - referentie CLIENTS(ID)
DATE_CMD
Datum waarop deze bestelling is ingevoerd
ANNULE
O (Ja) als de bestelling is geannuleerd en N (Nee) anders.

Image

6.1.4. de tabel DETAILS

Deze bevat de details van een bestelling, d.w.z. de referenties en aantallen van de bestelde boeken. De structuur is als volgt:

Image

NOCMD
Bestelnummer – externe sleutel die verwijst naar kolom NOCMD van de tabel COMMANDES
ISBN
Nummer van het bestelde boek - externe sleutel die verwijst naar kolom ISBN van de tabel LIVRES
QTE
Bestelde hoeveelheid

De inhoud zou als volgt kunnen zijn:

Image

Hierboven zien we dat bestelling nr. 3 (NOCMD) betrekking heeft op drie boeken. Dit betekent dat de klant drie boeken tegelijk heeft besteld. De referenties van deze klant zijn te vinden in de tabel [COMMANDES], waaruit blijkt dat bestelling nr. 3 is geplaatst door klant nr. 5. Uit de tabel [CLIENTS] blijkt dat klant nr. 5 het bedrijf NetLogos uit Segré is.

6.2. De bestelling SELECT

We willen hier onze kennis van de bestelling SELECT verdiepen door nieuwe syntaxisvormen ervan te presenteren.

6.2.1. Syntaxis van een query met meerdere tabellen

syntaxe
SELECT kolom1, kolom2, ...
FROM tabel1, tabel2, ..., tabelp
WHERE condition
ORDER BY ...
action
Het nieuwe hier is dat de kolommen kolom1, kolom2, ... afkomstig zijn uit verschillende tabellen tabel1, tabel2, ... Als twee tabellen kolommen met dezelfde naam hebben, wordt de dubbelzinnigheid opgeheven door de notatie tablei.colonnej. De notatie condition kan betrekking hebben op kolommen uit verschillende tabellen.

Werking

1
De cartesiaanse producttabel van table1, table2, ..., tablep wordt samengesteld. Als ni het aantal rijen van tablei is, dan heeft de samengestelde tabel dus n1*n2*...*np rijen die alle kolommen van de verschillende tabellen bevatten.
2
De condition van de WHERE wordt op deze tabel toegepast. Zo ontstaat een nieuwe tabel
3
Deze is gesorteerd volgens de wijze die is aangegeven in ORDER.
4
De kolommen die in SELECT zijn opgevraagd, worden weergegeven.

Voorbeelden

We gebruiken de eerder gepresenteerde tabellen. We willen de details zien van de bestellingen die na 25 september zijn geplaatst:

SQL>select details.nocmd,isbn,qte from commandes,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd

Image

Merk op dat achter FROM de naam wordt vermeld van alle tabellen waarvan de kolommen worden geraadpleegd. In het vorige voorbeeld behoren de geselecteerde kolommen allemaal tot de tabel DETAILS. De voorwaarde verwijst echter naar de tabel COMMANDES. Daarom moet deze laatste achter FROM worden vermeld. De bewerking waarbij wordt getoetst of kolommen uit twee verschillende tabellen gelijk zijn, wordt vaak een equijunctie genoemd.

De query SELECT had ook als volgt geschreven kunnen worden:

SQL> select details.nocmd,isbn,qte from commandes
    inner join details on details.nocmd=commandes.nocmd
    where commandes.datecmd>'25-sep-91'

Laten we verdergaan met onze voorbeelden. We willen hetzelfde resultaat als eerder, maar dan met de titel van het bestelde boek in plaats van het nummer ISBN:

SQL>select commandes.nocmd, articles.titre, details.qte
  from commandes,articles,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn

Image

Hetzelfde resultaat wordt verkregen met de volgende, minder leesbare query SQL:

SQL> select details.nocmd,articles.titre,details.qte from details
    inner join commandes on details.nocmd=commandes.nocmd
    inner join articles on  details.isbn=articles.isbn
    where commandes.datecmd>'25-sep-91'

Hierboven worden twee interne koppelingen gemaakt met de tabel [DETAILS]:

  • de ene met de tabel [COMMANDES] om toegang te krijgen tot de besteldatum van een boek
  • de ene met de tabel [ARTICLES] om toegang te krijgen tot de titel van het bestelde boek

Bovendien willen we de naam van de klant die de bestelling plaatst:

SQL>select commandes.nocmd, articles.titre, qte ,clients.nom
  from commandes,details,articles,clients
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn
  and commandes.idcli=clients.id

Image

Daarnaast willen we de besteldata en een weergave van deze data in aflopende volgorde:

SQL>select commandes.nocmd, commandes.datecmd, articles.titre, qte ,clients.nom
    from commandes,details,articles,clients
    where commandes.datecmd>'25-sep-91'
    and details.nocmd=commandes.nocmd
    and details.isbn=articles.isbn
    and commandes.idcli=clients.id
    order by commandes.datecmd descending

Image

Hier volgen enkele regels die bij het samenvoegen in acht moeten worden genomen:

  1. Achter SELECT zet je de kolommen die je in de weergave wilt zien. Als de kolom in verschillende tabellen voorkomt, zet je de naam van de tabel ervoor.
  2. Achter FROM zet je alle tabellen die door de SELECT worden doorzocht, dat wil zeggen de tabellen waarin de kolommen staan die achter SELECT en WHERE staan.

6.2.2. De auto-join

We willen weten welke boeken een verkoopprijs hebben die hoger is dan die van het boek 'Using SQL':

SQL>select a.titre from articles a, articles b
  where b.titre='Using SQL'
  and a.prixvente>b.prixvente

Image

De twee tabellen in de join zijn hier identiek: de tabel articles. Om ze van elkaar te onderscheiden, geven we ze een alias: from articles a, articles b. De alias van de eerste tabel heet a en die van de tweede b. Deze syntaxis kan ook worden gebruikt als de tabellen verschillend zijn. Wanneer een alias wordt gebruikt, moet deze overal in de opdracht SELECT worden gebruikt in plaats van de tabel waarnaar deze verwijst.

6.2.3. Externe join

We willen weten welke klanten in september iets hebben gekocht, met vermelding van de besteldatum. De overige klanten worden zonder deze datum weergegeven:

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where datecmd between '01-sep-91' and '30-sep-91'

Image

Het verbaast ons dat we hier niet het juiste resultaat krijgen. Alle klanten zouden in de tabel [CLIENTS] moeten staan, wat niet het geval is. Als we nadenken over hoe de externe join werkt, beseffen we dat de klanten die niets hebben gekocht, zijn gekoppeld aan een lege rij in de tabel COMMANDES en dus aan een lege datum (waarde NULL in de terminologie SQL). Deze datum voldoet dus niet aan de voorwaarde die is ingesteld voor de datum en de betreffende klant wordt niet weergegeven. Laten we iets anders proberen:

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where (commandes.datecmd between '01-sep-91' and '30-sep-91')
        or (commandes.datecmd is null)

Image

Dit keer krijgen we het juiste antwoord op onze vraag.

6.2.4. Geneste zoekopdrachten

syntaxe
SELECT kolom[s] FROM tabel[s]
WHERE uitdrukking operator query
ORDER BY ...
fonctionnement
requête is een commando SELECT dat een groep van 0, 1 of meerdere waarden oplevert. We hebben dan een voorwaarde WHERE van het type
uitdrukking operator (val1, val2, ..., vali)
expression en vali moeten van hetzelfde type zijn. Als de query slechts één waarde oplevert, komt men uit bij een voorwaarde van het type
uitdrukking operator waarde
die we goed kennen. Als de query een lijst met waarden oplevert, kunnen we de volgende operatoren gebruiken:
IN
expression IN (val1, val2, ..., vali): waar als expression de waarde heeft van een van de elementen uit de lijst vali.
NOT IN
omgekeerd van IN
ANY
moet worden voorafgegaan door =, !=, >, >=, <, <=
expression >= ANY (val1, val2, .., valn): waar als expression >= is aan een van de waarden vali uit de lijst
ALL
moet worden voorafgegaan door =, !=, >, >=, <, <=
expression >= ALL (val1, val2, .., valn): waar als de uitdrukking >= is aan alle waarden vali uit de lijst
EXISTS 
query: waar als de requête ten minste één rij retourneert.

Voorbeelden

We nemen de vraag die al met een equi-join is opgelost: toon de titels met een verkoopprijs die hoger is dan die van het boek 'Using SQL'.

SQL>select titre from ARTICLES
    where prixvente > (select prixvente from ARTICLES where titre='Using SQL')

Image

Deze oplossing lijkt intuïtiever dan die met de equijunctie. We voeren eerst een filter uit op SELECT en vervolgens een tweede filter op het verkregen resultaat. Op deze manier kunnen we meerdere filters achter elkaar toepassen.

We willen weten welke titels een verkoopprijs hebben die hoger is dan de gemiddelde verkoopprijs:

SQL> select titre from ARTICLES
    where prixvente > (select avg(prixvente) from ARTICLES)

Image

Welke klanten hebben de titels besteld die het resultaat zijn van de vorige zoekopdracht?

SQL>select distinct idcli from COMMANDES,DETAILS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd

Image

Toelichting

  1. in de tabel DETAILS worden de codes ISBN geselecteerd die voorkomen bij boeken waarvan de prijs hoger is dan de gemiddelde prijs van de boeken.
  2. In de geselecteerde rijen uit de vorige stap ontbreekt de klantcode IDCLI. Deze bevindt zich in de tabel COMMANDES. De koppeling tussen de twee tabellen gebeurt via het bestelnummer NOCMD, vandaar de equijunctie COMMANDES.nocmd=DETAILS.nocmd.
  3. Eenzelfde klant kan een van de betreffende boeken meerdere keren hebben gekocht, in welk geval zijn code IDCLI meerdere keren voorkomt. Om dit te voorkomen, plaatsen we het sleutelwoord DISTINCT achter SELECT. DISTINCT verwijdert in het algemeen duplicaten uit de resultaatregels van een SELECT.
  4. Om de naam van de klant te verkrijgen, zouden we een extra equijunctie moeten uitvoeren tussen de tabellen COMMANDES en CLIENTS, zoals te zien is in de volgende query.
SQL> select distinct CLIENTS.nom from COMMANDES,DETAILS,CLIENTS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd
    and COMMANDES.IDCLI=CLIENTS.ID

Image

Zoek klanten die sinds 24 september geen bestelling hebben geplaatst:

SQL>select nom from CLIENTS
    where clients.id not in
    (select distinct commandes.idcli from commandes where datecmd>='24-sep-91')

Image

We hebben gezien dat we rijen ook op een andere manier kunnen filteren dan met de clausule WHERE: door de clausule HAVING te gebruiken in combinatie met de clausules GROUP en BY. De clausule HAVING filtert groepen regels.

Net als bij de clausule WHERE is de syntaxis


     HAVING expression opérateur requête 

is mogelijk, met de reeds genoemde beperking dat expression een van de uitdrukkingen expri in de clausule


     GROUP BY expr1, expr2, ...

Voorbeelden

Wat zijn de verkochte aantallen voor boeken met meer dan 200F?

Laten we eerst de verkochte aantallen per titel weergeven:

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre

Image

Laten we nu de titels filteren:

SQL> select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre
    having titre in (select titre from ARTICLES where prixvente>200)

Image

Misschien zou het duidelijker zijn geweest om het als volgt te schrijven:

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    and ARTICLES.prixvente>200
    group by titre

Image

6.2.5. Gekoppelde query's

Bij geneste query's is er sprake van een bovenliggende query (de meest buitenste query) en een onderliggende query (de meest binnenste query). De bovenliggende query wordt pas geëvalueerd nadat de onderliggende query volledig is geëvalueerd.

Gekoppelde query’s hebben dezelfde syntaxis, op het volgende detail na: de dochterquery voert een join uit op de tabel van de moederquery. In dit geval wordt de combinatie van moeder- en dochterquery herhaaldelijk geëvalueerd voor elke rij van de moeder-tabel.

Voorbeeld

We nemen het voorbeeld waarin we de namen willen hebben van klanten die sinds 24 september geen bestelling hebben geplaatst:

SQL> 
select nom from clients
    where not exists
        (select idcli from commandes
            where datecmd>='24-sep-91'
                and commandes.idcli=clients.id)

Image

De bovenliggende query wordt uitgevoerd op de tabel clients. De onderliggende query voert een join uit tussen de tabellen clients en commandes. We hebben dus te maken met een gecorreleerde query. Voor elke rij in de tabel clients wordt de dochterquery uitgevoerd: deze zoekt de klantcode id in de bestellingen die na 24 september zijn geplaatst. Als deze niet wordt gevonden (not exists), wordt de naam van de klant weergegeven. Vervolgens gaan we naar de volgende regel van de tabel clients.

6.2.6. Selectiecriteria voor het schrijven van de SELECT

We hebben al meerdere keren gezien dat het mogelijk is om hetzelfde resultaat te verkrijgen door de tabel SELECT op verschillende manieren te doorlopen. Laten we een voorbeeld nemen: klanten weergeven die iets hebben besteld:

Join

SQL>
select distinct nom from clients,commandes
    where clients.id=commandes.idcli

Image

Geneste query's

SQL> 
select nom from clients
    where id in (select idcli from commandes)

levert hetzelfde resultaat op.

Gecorreleerde zoekopdrachten

SQL>
select nom from clients
    where exists (select * from commandes where commandes.idcli=clients.id)

levert hetzelfde resultaat op.

De auteurs Christian MAREE en Guy LEDANT stellen in hun boek 'SQL, Inleiding, Programmeren en Beheersing' enkele selectiecriteria voor:

Prestaties

De gebruiker weet niet hoe het SGBD erin slaagt de gevraagde resultaten te vinden. Alleen door ervaring zal hij dus ontdekken dat de ene schrijfwijze beter presteert dan de andere. MAREE en LEDANT stellen op basis van ervaring dat gecorreleerde query’s over het algemeen trager lijken te zijn dan geneste query’s of joins.

Formulering

De formulering met geneste query’s is vaak leesbaarder en intuïtiever dan een join. Ze is echter niet altijd bruikbaar. Er zijn met name twee punten waar je op moet letten:

  • De tabellen die de argumentkolommen van de SELECT (SELECT col1, col2, ...) bevatten, moeten na het sleutelwoord FROM worden genoemd. Vervolgens wordt het cartesiaanse product van deze tabellen uitgevoerd, wat een join wordt genoemd.
  • Wanneer de query resultaten uit één enkele tabel weergeeft en het filteren van de rijen uit die tabel het raadplegen van een andere tabel vereist, kunnen geneste queries worden gebruikt.

6.3. Syntaxisuitbreidingen

Omwille van het gebruiksgemak hebben we meestal verkorte syntaxis van de verschillende commando’s gepresenteerd. In deze paragraaf presenteren we de uitgebreide syntaxis. Deze spreken voor zich, omdat ze analoog zijn aan die van het uitgebreid besproken commando SELECT.

INSERT

syntaxe1
INSERT INTO table (col1, col2, ..) VALUES (val1, val2, ...)
syntaxe2
INSERT INTO table (kolom1, kolom2, ..) (requête)
explication
Deze twee syntaxisvormen zijn gepresenteerd

DELETE

syntaxe1
DELETE FROM table WHERE condition
explication
Deze syntaxis is bekend. Hieraan kan worden toegevoegd dat de voorwaarde een query kan bevatten met de syntaxis WHERE uitdrukking operator (query)

UPDATE

syntaxe1
UPDATE table
SET kol1=uitdruk1, kol2=uitdruk2, ...
WHERE condition
explication
Deze syntaxis is al eerder besproken. Hieraan kunnen we toevoegen dat de voorwaarde een query kan bevatten met de syntaxis WHERE uitdrukking operator (query)
syntaxe2
UPDATE table
SET (kol1, kol2, ..) = query1, (kolA, kolB, ..) = query2, ...
WHERE condition
explication
De waarden die aan de verschillende kolommen worden toegewezen, kunnen afkomstig zijn uit een query.