Subquery#

%LOAD ../data/employees.db

Met een subquery kun je eerst informatie opvragen die je nog niet weet. Op basis van die informatie kun je vervolgens een nieuwe query maken. Stel bijvoorbeeld dat je een overzicht wil maken van alle werknemers die in dienst zijn gekomen na Peter Vargas. Dan zul je eerst moeten weten wanneer Peter Vargas in dienst is gekomen!

Eerst maar eens een heel foute manier!

SELECT first_name, last_name, hire_date
FROM employees
WHERE hire_date > '1998-07-09';
first_name last_name hire_date
Diana Lorentz 1999-02-07
Kevin Mourgos 1999-11-16
Eleni Zlotkey 2000-01-29
Kimberely Grant 1999-05-24

Deze manier is erg verkeerd omdat je niet kunt weten dat de hire_date van Peter Vargas 1998-07-09 is. Dat heb je waarschijnlijk opgezocht in de papieren versie of in PHPMyAdmin in de tabel employees. En dat mag dus niet!

Je moet eerst de hire_date van Peter Vargas ophalen met een query.

SELECT hire_date
FROM employees
WHERE last_name = 'Vargas';
hire_date
1998-07-09

Dan neem je die query en die zet je tussen haakjes. Dit gedeelte noemen we de subquery of ook wel de inner query.

(SELECT hire_date
FROM employees
WHERE last_name = 'Vargas')

En dan zetten we daar een andere query voor. Dit gedeelte is de main query of ook wel de outer query genoemd.

SELECT first_name, last_name, hire_date
FROM employees
WHERE hire_date >
    (SELECT hire_date
     FROM employees
     WHERE last_name = 'Vargas');
first_name last_name hire_date
Diana Lorentz 1999-02-07
Kevin Mourgos 1999-11-16
Eleni Zlotkey 2000-01-29
Kimberely Grant 1999-05-24

Een ander voorbeeld: Wie werkt er op dezelfde afdeling als Lex De Haan?

Eerst vraag je het department_id op van De Haan.

SELECT department_id
FROM employees
WHERE last_name = 'De Haan'; 
department_id
90

Neem die query en zet die tussen haakjes.

(SELECT department_id
 FROM employees
 WHERE last_name = 'De Haan')

En dan zetten we daar een andere query voor.

SELECT last_name
FROM employees
WHERE department_id =
    (SELECT department_id
     FROM employees
     WHERE last_name = 'De Haan');
last_name
King
Kochhar
De Haan

Wil je daar De Haan zelf niet bij? Dan zet je in de WHERE met != erbij dat de achternaam niet gelijk mag zijn aan De Haan.

SELECT last_name
FROM employees
WHERE last_name != 'De Haan' AND department_id =
    (SELECT department_id
     FROM employees
     WHERE last_name = 'De Haan');
last_name
King
Kochhar

Er zijn twee soorten subquery’s:

  • single-row subquery’s

  • multiple-row subquery’s

Single-row#

  • Single-row subquery’s geven maar één rij terug.

  • Ze gebruiken single-row vergelijkingsoperatoren:

    • =  >  >=  <=  <> !=

  • De subquery komt tussen haakjes.

  • Voor de bovenste en de onderste query kun je verschillende tabellen gebruiken.

  • Je mag maar één keer ORDER BY gebruiken en dat moet de laatste regel zijn van de bovenste query (main SELECT statement). Die komt dus helemaal onderaan.

Wie werkt er allemaal op de afdeling Marketing? Dat zijn de personen met hetzelfde department_id, het department_id van de afdeling Marketing.

Eerst haal je dus het department_id op van de afdeling Marketing. Dat staat in de tabel departments.

SELECT department_id
FROM departments
WHERE department_name = 'Marketing';
department_id
20

Zet die query tussen haakjes en zet er een andere query voor. Voor de duidelijkheid laten we de subquery inspringen.

SELECT last_name, job_id, department_id
FROM employees
WHERE department_id =
 (SELECT department_id
  FROM departments
  WHERE department_name = 'Marketing')
ORDER BY job_id;
last_name job_id department_id
Hartstein MK_MAN 20
Fay MK_REP 20

Je kunt meerdere subquery’s gebruiken.

SELECT last_name, job_id, salary, department_id
FROM employees
WHERE job_id =
 (SELECT job_id
  FROM employees
  WHERE employee_id = 141)
AND department_id =
 (SELECT department_id
  FROM departments
  WHERE location_id = 1500);
last_name job_id salary department_id
Rajs ST_CLERK 3500 50
Davies ST_CLERK 3100 50
Matos ST_CLERK 2600 50
Vargas ST_CLERK 2500 50

Multiple-row#

  • Multiple-row subquery’s kunnen meer dan één resultaat opleveren. Dan kun je de single-row operators (zoals bijvoorbeeld het = teken) niet gebruiken.

  • Ze gebruiken multiple-row vergelijkingsoperatoren:

    • IN  ANY  ALL

  • De NOT operators kan bij alle drie gebruikt worden.

Naar IN zullen we kijken maar ANY en ALL laten we buiten beschouwing in deze cursus.

Met de IN operator in de outer query kun je alle rijen selecteren die voorkomen in een lijstje van waarden dat gemaakt is met de inner query.

Stel dat we alle namen van werknemers willen die in hetzelfde jaar zijn aangenomen als werknemers van afdeling 90. Eerst moeten we een subquery (inner query) maken om een lijstje op te halen met de jaren waarin werknemers van afdeling 90 zijn aangenomen.

SELECT strftime('%Y', hire_date)
FROM employees
WHERE department_id = 90;
strftime('%Y', hire_date)
1987
1989
1993

Die subquery zetten we tussen haakjes en daar zetten we de outer query boven.

SELECT last_name, strftime('%Y', hire_date) as year
FROM employees
WHERE year IN
 (SELECT strftime('%Y', hire_date)
  FROM employees
  WHERE department_id = 90);
last_name year
King 1987
Kochhar 1989
De Haan 1993
Whalen 1987

Multiple-row subquery’s zijn altijd veiliger dan single-row subquery’s. Vergelijk deze twee query’s met elkaar.

SELECT first_name, last_name, job_id
FROM employees
WHERE job_id IN
 (SELECT JOB_ID
  FROM employees
  WHERE last_name= 'Ernst' );
first_name last_name job_id
Alexander Hunold IT_PROG
Bruce Ernst IT_PROG
Diana Lorentz IT_PROG
SELECT first_name, last_name, job_id
FROM employees
WHERE job_id =
 (SELECT JOB_ID
  FROM employees
  WHERE last_name = 'Ernst' );
first_name last_name job_id
Alexander Hunold IT_PROG
Bruce Ernst IT_PROG
Diana Lorentz IT_PROG

Ze hebben beide hetzelfde resultaat.

Tip

Waar je = zou gebruiken, werkt IN dus ook altijd. Als je twijfelt: gebruik IN in plaats van =.

Exists#

EXISTS en NOT EXISTS kunnen worden gebruikt voor subquery’s.

Stel dat je wil onderzoeken welke department_id’s in de tabel departments staan en ook bestaan (exist) in de tabel employees.

SELECT department_id
FROM departments
WHERE EXISTS
   (SELECT * FROM employees
    WHERE departments.department_id = employees.department_id)
ORDER BY department_id;
department_id
10
20
50
60
80
90
110

Of juist het tegenovergestelde. Welk department_id komt wel voor in departments maar bestaat niet (not exists) niet in employees?

SELECT department_id
FROM departments
WHERE NOT EXISTS
   (SELECT * FROM employees
    WHERE departments.department_id = employees.department_id)
ORDER BY department_id;
department_id
190

Welke werknemers zijn geen manager?

Dat kun je onderzoeken met een SELF JOIN en een SUBQUERY. Bij een SELF JOIN gebruik je dezelfde tabel twee maal met verschillende aliassen. In dit voorbeeld wordt de tabel employees één keer gebruikt als emp en één keer als mgr.

SELECT last_name AS not_a_manager
FROM employees e
WHERE NOT EXISTS
 (SELECT *
  FROM employees m
  WHERE m.manager_id = e.employee_id);
not_a_manager
Ernst
Lorentz
Rajs
Davies
Matos
Vargas
Abel
Taylor
Grant
Whalen
Fay
Gietz

Null#

Het kan zijn dat bij een single-row subquery de subquery als resultaat NULL oplevert. Zo heeft Grant bijvoorbeeld geen department_id.

SELECT department_id
FROM employees
WHERE last_name = 'Grant';
department_id

En we stellen de vraag wie er op dezelfde afdeling werken als Grant.

SELECT last_name
FROM employees
WHERE department_id =
    (SELECT department_id
    FROM employees
    WHERE last_name = 'Grant');
last_name

Dan krijg je een leeg resultaat terug: geen enkele rij dus.

leeg resultaat in MySQL

MySQL geef in dit geval de melding:

Een vergelijking met NULL levert altijd FALSE op. En als een conditie FALSE is, worden de rijen uitgesloten van het query resultaat.

Als bij een multiple-row query één van de waarden van de subquery NULL is, dan worden enkel de waarden die niet NULL zijn gebruikt.

Welke werknemers zijn er allemaal manager? Dat zijn de manager_id’s die voorkomen in de lijst van employee_id’s.

Eerst halen we alle manager_id’s op. Daar zit één NULL waarde bij. King is de grootste baas en hij heeft geen manager.

SELECT DISTINCT manager_id
FROM employees;
manager_id
100
102
103
124
149
101
201
205

Die subquery (inner query) zetten we tussen haakjes en daar zetten we de outer query boven.

SELECT last_name, employee_id
FROM employees
WHERE employee_id IN
 (SELECT DISTINCT manager_id
  FROM employees);
last_name employee_id
King 100
Kochhar 101
De Haan 102
Hunold 103
Mourgos 124
Zlotkey 149
Hartstein 201
Higgins 205

Het is altijd oppassen met NULLS in de subquery bij het gebruik van IN of NOT IN. Als je niet zeker weet of een NULL voor gaat komen in de subquery en er misschien een verkeerd resultaat uit volgt, kun je beter die NULL uitsluiten met een WHERE.

SELECT last_name, employee_id
FROM employees
WHERE employee_id IN
 (SELECT DISTINCT manager_id
  FROM employees
  WHERE manager_id IS NOT NULL);
last_name employee_id
King 100
Kochhar 101
De Haan 102
Hunold 103
Mourgos 124
Zlotkey 149
Hartstein 201
Higgins 205

Groepsfuncties#

Het is mogelijk groepsfuncties te gebruiken in subquery’s, zoals MIN, MAX, SUM en AVG.

Wie verdient er minder dan het gemiddelde salaris?

Eerst maak je de query voor het gemiddelde salaris met de groepsfunctie AVG.

SELECT AVG(salary)
FROM employees;
AVG(salary)
8775.0

Dat zet je tussen haakjes en daar zet je een andere query voor.

SELECT last_name, salary
FROM employees
WHERE salary <
 (SELECT AVG(salary)
  FROM employees);
last_name salary
Ernst 6000
Lorentz 4200
Mourgos 5800
Rajs 3500
Davies 3100
Matos 2600
Vargas 2500
Taylor 8600
Grant 7000
Whalen 4400
Fay 6000
Gietz 8300