‘Inner Select’ statements in MySQL

Na joinen en group by, de 3de episode in mysql. Het gaat hier over inner selects. Zeg maar een tweede select in een andere genest. Dat kan behoorlijk praktisch zijn in je where clausule. Je kan deze dingen ook gaan gebruiken in insert, update en delete statements (dat valt onder DML: Data Manipulation Language), maar daar ga ik het in deze tutorial niet over hebben. We houden het louter bij de retrieval, of laat ons zeggen; dé select. Laat ik deze keer mijn vertrouwde voorbeeld maar weer gebruiken, we kennen het onderhand wel:

Nogmaals, voor de verklaring van deze structuur (mocht hij je niet duidelijk zijn), zoek dan even de tutorial ‘Join in MySQL’. Misschien kan ik best even beginnen met een voorbeeld van zo’n inner select. We hadden het in het voorbeeld bij 'Group By' over de laaste reactie op een bepaald bericht. (In de veronderstelling dat het bericht met de hoogste id, de laatste reactie is.) Daarbij konden we geen andere gegevens ophalen dan het id nummer, dat kan nu wel:
SELECT * FROM Reac t i e W HERE I d = (SELECT max(Id) FROM Reactie );

Waarin ik de inner select even rood oplicht. Let erop dat deze steeds tussen haakjes staat. Je ziet meteen wat er gebeurt: de inner select wordt uitgevoerd en geeft de hoogste 'id' in de tabel met reacties terug. Daarna vergelijkt de buitenste select de id van iedere rij met de uitvoer van de 'inner select'. Voor de mensen die de ‘Group By’ tutorial nog niet lazen. We gebruiken deze structuur omdat het gebruik van een analytische functie in de where clausule uit den boze is. Dit is zeer eenvoudig te verklaren door het feit dat de voorwaarde in een where clausule op iedere rij apart moet kunnen toegepast worden. Zo kan je in dit geval niet aan één enkele rij zien of ze al dan niet de grootste id heeft.

Let er wel op dat je inner select in dit geval slechts 1 waarde teruggeeft. Een analytische functie leent zich er ideaal toe, maar een zeer stricte where clausule kan tot hetzelfde resultaat komen. Wanneer je de gelijkheid wil halen uit een rij van mogelijkheden is er een andere truk. Ik illustreer even met een voorbeeld. Je wil bijvoorbeeld alle mensen uit de database halen die ooit een bericht schreven. Dan kan dat eenvoudig door:
SELECT * FROM Lid WHERE Lidnr IN SELECT Schrijver FROM Bericht ( );

Merk op dat er uit deze inner select meerdere resultaten voorkomen. Wil je checken of het lidnr in die lijst voorkomt, gebruik dan het codewoord IN. Iets verder gevorderde lezers zullen nu terecht opmerken dat bovenstaand probleem ook op te lossen is met een join. En effectief:
SELECT DISTINCT l.* FROM Bericht AS b, Lid AS l WHERE b.Schrijver = l.Lidnr

Er zullen nu waarschijnlijk meer voorbeelden komen die op verschillende manier op te lossen zijn. Dit aangezien ik de voorbeelden zo eenvoudig en duidelijk mogelijk probeer te houden. Wanneer je echter ingewikkelde query’s gaat schrijven, zal je merken dat op een bepaald moment je voorkeur zal uitgaan voor een join of inner select. Zeker wanneer je beide technieken in één query nodig hebt. Handig in gebruik is ook, wanneer je de IN dan toch min of meer onder knie hebt, de NOT IN, zoals het structuur al zegt, geeft mysql nu de rijen terug die niet in de lijst voorkomen. Een voorbeeld. We willen om inconsistentie in onze database te vermijden even controleren of er berichten bestaan die een id in voortzetting hebben waarvan het oorspronkelijke bericht werd verwijderd. M.a.w. de voortzetting bevat een id, maar wanneer we die id volgen, vinden we geen bericht:
SELECT * FROM Bericht WHERE Voortzetting NOT IN ( SELECT Id FROM Bericht ) AND Voortzetting IS NOT NULL;

Waarin we alle rijen ophalen waarvan de voortzetting niet leeg is, maar de id in voortzetting ongeldig is. Het is uiteraard ook mogelijk een where clausule in zo’n inner select in te bouwen. Willen we nu dezelfde bovenstaande controle uitvoeren. Met als extra dat oorspronkelijke berichten ook voorzien moeten zijn van schrijver.
SELECT * FROM Bericht WHERE Voortzetting NOT IN ( SELECT Id FROM Bericht WHERE Schrijver IS NOT ) NULL AND Voortzetting IS NOT NULL;

We halen nu eigenlijk in de inner select alle berichten op waarvan de schrijver is ingevuld. Nu geven we alle berichten weer waarvan de voortzetting is ingevuld, maar deze nummer niet overeenkomt met één van de id’s uit die lijst.

meer kolommen vergelijken
Bij inner selects moet je er ook altijd voor zorgen dat het aantal kolommen uit de where overeenkomen met het aantal dat de inner select teruggeeft. Tot hier toe, hebben we steeds slechts 1 kolom gebruikt. Wil je nu op meer kolommen testen uit de inner select dan zet je alsook de kolommen in je where tussen haakjes, de syntax:
WHERE (kolom1,kolom2) inSELECT kolom1b,kolom2b FROM tabel ( );

Het spijt me, maar helaas kan ik hieromtrent even geen voorbeeld aanhalen. Het wordt dan ook weinig gebruikt in de wereld van eenvoudige bericht/reactie systemen. Dit kan alleszins wel erg praktisch zijn als je bijvoorbeeld een gedeelde primaire sleutel hebt (waar ik het over had in de tutorial over joinen). Aangezien we dat hier niet hebben, laat ik een voorbeeld achterwege. Mocht je dit ooit nodig hebben en er duiken problemen op, dan weet je me maar te vinden. Bij deze sluit ik de trilogie ‘Join, group by en inner selects’ af. Een combinatie van extra functies in mysql die je query’s tot het uiterste kunnen drijven. Nog veel succes.

Sign up to vote on this title
UsefulNot useful