Alle code-oefeningen
SQLPostgreSQLGevorderd7 vragen

Recursieve CTE — een stuklijst uitklappen zonder in een lus te blijven hangen

WITH RECURSIVE van anker tot afbreekconditie, met cyclusdetectie via een padarray. Waarom UNION ALL geen stijlkeuze is en waarom een dieptelimiet ook mét cyclusdetectie nodig blijft.

Een fiets bestaat uit een frame, twee wielen en een aandrijving. Een wiel bestaat uit een velg, spaken en een band. Zo diep als je wilt. Deze functie klapt zo'n boom uit en rekent onderweg door hoeveel je van elk onderdeel nodig hebt.

WITH RECURSIVE is het stuk SQL waar de meeste mensen op afhaken, meestal omdat de uitleg begint bij de syntaxis in plaats van bij wat er gebeurt. Lees het als: begin met één rij, en blijf er zolang mogelijk nieuwe rijen aan vastplakken.

CREATE OR REPLACE FUNCTION productie.stuklijst_uitklappen(
    p_artikel_id bigint,
    p_aantal     numeric DEFAULT 1,
    p_max_diepte int     DEFAULT 20
)
RETURNS TABLE (
    niveau        int,
    artikel_id    bigint,
    omschrijving  text,
    aantal_totaal numeric,
    pad           bigint[],
    is_cyclus     boolean
)
LANGUAGE sql
STABLE
AS $$
    WITH RECURSIVE uitklap AS (
        -- Ankerdeel: het artikel waar we mee beginnen.
        SELECT 0            AS niveau,
               a.id         AS artikel_id,
               a.omschrijving,
               p_aantal     AS aantal_totaal,
               ARRAY[a.id]  AS pad,
               false        AS is_cyclus
        FROM   productie.artikelen a
        WHERE  a.id = p_artikel_id

        UNION ALL

        -- Recursieve deel: elk onderdeel van wat we al gevonden hebben.
        SELECT u.niveau + 1,
               c.onderdeel_id,
               a.omschrijving,
               u.aantal_totaal * c.aantal,
               u.pad || c.onderdeel_id,
               c.onderdeel_id = ANY(u.pad)
        FROM   uitklap u
        JOIN   productie.stuklijstregels c ON c.artikel_id = u.artikel_id
        JOIN   productie.artikelen a       ON a.id = c.onderdeel_id
        WHERE  NOT u.is_cyclus
          AND  u.niveau < p_max_diepte
    )
    SELECT niveau, artikel_id, omschrijving, aantal_totaal, pad, is_cyclus
    FROM   uitklap
    ORDER  BY pad;
$$;
Vraag 1Opwarmer

Wijs het ankerdeel en het recursieve deel aan. Wat zorgt ervoor dat dit ooit stopt?

Vraag 2Waarom zo

Er staat UNION ALL. Wat zou er veranderen als je er UNION van maakt?

Vraag 3Semantiek

Wat staat er in aantal_totaal, en waarom wordt er vermenigvuldigd in plaats van opgeteld?

Vraag 4Cyclus

De cyclusdetectie is opgesplitst: c.onderdeel_id = ANY(u.pad) staat in de SELECT, maar NOT u.is_cyclus staat in de WHERE. Waarom niet allebei op dezelfde plek?

Vraag 5Randgeval

Er is cyclusdetectie. Waarom is p_max_diepte dan nog nodig?

Vraag 6Sortering

ORDER BY pad — waarom sorteren op een array, en wat levert dat op?

Vraag 7Expert

Het pad-array groeit bij elke stap. Wat kost dat, en biedt PostgreSQL zelf iets beters?

Volgende oefening