Alle code-oefeningen
SQLPostgreSQLInstap6 vragen

Maandomzet per regio — een rapportagefunctie lezen

Een SQL-functie met CTE's, een HAVING-filter en een window-functie. Begin hier als je stored procedures nog niet dagelijks leest.

Deze functie draait elke maandagochtend voor het management-dashboard. Hij is niet lang, maar er zitten vier dingen in die je in vrijwel elke rapportagequery terugziet: een filter op een datumbereik, een aggregatie met een HAVING, een berekend gemiddelde en een window-functie voor een percentage van het totaal.

Lees hem eerst helemaal door zonder naar de vragen te kijken. Probeer daarna per vraag eerst zelf een antwoord te formuleren voordat je op Toon antwoord klikt — anders leer je vooral hoe overtuigend het antwoord klinkt.

CREATE OR REPLACE FUNCTION rapportage.maandomzet_per_regio(
    p_jaar      int,
    p_min_omzet numeric DEFAULT 0
)
RETURNS TABLE (
    regio           text,
    maand           date,
    omzet           numeric,
    orders          bigint,
    gem_orderwaarde numeric,
    aandeel_pct     numeric
)
LANGUAGE sql
STABLE
AS $$
    WITH betaalde_orders AS (
        SELECT o.id,
               o.klant_id,
               date_trunc('month', o.besteld_op)::date AS maand,
               o.totaal_excl_btw
        FROM   verkoop.orders o
        WHERE  o.status = 'betaald'
          AND  o.besteld_op >= make_date(p_jaar, 1, 1)
          AND  o.besteld_op <  make_date(p_jaar + 1, 1, 1)
    ),
    per_regio AS (
        SELECT k.regio,
               b.maand,
               SUM(b.totaal_excl_btw) AS omzet,
               COUNT(*)               AS orders
        FROM   betaalde_orders b
        JOIN   verkoop.klanten k ON k.id = b.klant_id
        GROUP  BY k.regio, b.maand
        HAVING SUM(b.totaal_excl_btw) >= p_min_omzet
    )
    SELECT r.regio,
           r.maand,
           r.omzet,
           r.orders,
           ROUND(r.omzet / r.orders, 2) AS gem_orderwaarde,
           ROUND(100 * r.omzet / SUM(r.omzet) OVER (PARTITION BY r.maand), 1) AS aandeel_pct
    FROM   per_regio r
    ORDER  BY r.maand, r.omzet DESC;
$$;
Vraag 1Opwarmer

Wat staat er precies in de kolom maand, en waarom staat er ::date achter?

Vraag 2Waarom zo

Het jaarfilter staat er als >= make_date(p_jaar, 1, 1) AND < make_date(p_jaar + 1, 1, 1). Waarom niet gewoon WHERE EXTRACT(year FROM o.besteld_op) = p_jaar?

Vraag 3Semantiek

Wat berekent SUM(r.omzet) OVER (PARTITION BY r.maand), en tellen de waarden in aandeel_pct altijd op tot 100?

Vraag 4Randgeval

Kan deze functie een division by zero opleveren? Er wordt op twee plekken gedeeld.

Vraag 5Valkuil

JOIN verkoop.klanten k ON k.id = b.klant_id — welke twee dingen gaan hier stil mis met de cijfers?

Vraag 6Uitvoering

De functie is gemarkeerd als STABLE en geschreven in LANGUAGE sql in plaats van plpgsql. Wat levert die combinatie op, en wat verandert er bij VOLATILE?

Volgende oefening