Structures Itératives et Boucles en PL/pgSQL

@joekakone

Joseph Konka @joekakone

03/03/2026 · 8 min

Explorez les structures de boucles en base de données : boucle de base LOOP avec EXIT WHEN, boucle conditionnelle WHILE, boucle numérique FOR, et itération par curseur FOR IN SELECT.

Dans un moteur de base de données relationnelle, la règle fondamentale de performance est de travailler en mode ensembliste (set-based) : une seule instruction UPDATE ou INSERT INTO ... SELECT est toujours des centaines de fois plus rapide qu'une boucle mettant à jour les lignes une par une.

Cependant, dans l'administration de bases de données et l'ingénierie ETL, les boucles procédurales sont indispensables pour : * Traiter des données par lots (batches) pour ne pas saturer le journal de transactions (WAL). * Générer dynamiquement des partitions ou des tables par date. * Itérer sur un catalogue de tables pour exécuter des scripts de maintenance.


1. La boucle inconditionnelle : LOOP ... EXIT WHEN#

C'est la structure de répétition la plus élémentaire en PL/pgSQL. Elle s'exécute indéfiniment jusqu'à ce qu'une instruction EXIT soit rencontrée.

DO
$$
DECLARE
    v_compteur INTEGER := 1;
    v_total NUMERIC := 0;
BEGIN
    LOOP
        v_total := v_total + (v_compteur * 10);
        RAISE NOTICE 'Itération % : Total cumulé = %', v_compteur, v_total;

        v_compteur := v_compteur + 1;

        -- Condition de sortie obligatoire pour éviter la boucle infinie
        EXIT WHEN v_compteur > 5;
    END LOOP;

    RAISE NOTICE 'Sortie de boucle réussie !';
END;
$$ LANGUAGE plpgsql;

[!TIP] Vous pouvez également utiliser l'instruction CONTINUE WHEN condition; pour interrompre le tour de boucle en cours et passer immédiatement à l'itération suivante sans exécuter les instructions restantes.


2. La boucle conditionnelle : WHILE ... LOOP#

La boucle WHILE évalue une condition booléenne au début de chaque cycle. Si la condition est fausse dès le départ, le corps de la boucle n'est jamais exécuté.

DO
$$
DECLARE
    v_stock_disponible INTEGER := 50;
    v_taille_lot CONSTANT INTEGER := 12;
    v_lots_expedies INTEGER := 0;
BEGIN
    WHILE v_stock_disponible >= v_taille_lot LOOP
        v_stock_disponible := v_stock_disponible - v_taille_lot;
        v_lots_expedies := v_lots_expedies + 1;

        RAISE NOTICE 'Lot #% expédié. Stock restant : % unités.', 
            v_lots_expedies, v_stock_disponible;
    END LOOP;

    RAISE NOTICE 'Fin des expéditions : reliquat de % unité(s) en stock.', v_stock_disponible;
END;
$$ LANGUAGE plpgsql;

3. La boucle bornée numérique : FOR ... IN 1..N#

Lorsque vous connaissez à l'avance le nombre d'itérations, la boucle FOR est la solution la plus concise.

DO
$$
DECLARE
    v_table_cible TEXT;
BEGIN
    -- Itération croissante de 1 à 4
    FOR i IN 1..4 LOOP
        v_table_cible := 'analytics.fct_ventes_t' || i;
        RAISE NOTICE 'Génération de l index pour le trimestre T% (Table: %)', i, v_table_cible;
    END LOOP;

    -- Itération décroissante avec REVERSE et pas personnalisé avec BY
    FOR j IN REVERSE 10..2 BY 2 LOOP
        RAISE NOTICE 'Décompte pair : %', j;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

[!NOTE] Remarquez que la variable de compteur i ou j n'a pas besoin d'être déclarée dans la section DECLARE ! PostgreSQL crée et détruit automatiquement une variable locale de type INTEGER pendant toute la durée de la boucle FOR.


4. La boucle sur résultats SQL : FOR record IN SELECT ... LOOP#

C'est sans comparaison la structure la plus utilisée et la plus utile pour un Data Engineer. Elle permet de parcourir les enregistrements d'une requête SQL sans manipuler manuellement de curseur explicite (OPEN, FETCH, CLOSE).

Exemple : Traitement par région et catégorie#

DO
$$
DECLARE
    -- Déclaration d'une variable générique pour accueillir chaque ligne
    rec RECORD;
    v_seuil_critique CONSTANT NUMERIC := 100000.00;
BEGIN
    -- Itération sur le résultat d'une requête SQL complexe
    FOR rec IN 
        SELECT 
            region,
            categorie,
            SUM(montant) AS ca_total,
            COUNT(*) AS nb_transactions
        FROM prod.ventes
        GROUP BY region, categorie
        ORDER BY ca_total DESC
    LOOP
        IF rec.ca_total >= v_seuil_critique THEN
            RAISE NOTICE '[STAR] Région % | Catégorie % : % € (% ventes)',
                rec.region, rec.categorie, rec.ca_total, rec.nb_transactions;
        ELSE
            RAISE NOTICE '[NORMAL] Région % | Catégorie % : % €',
                rec.region, rec.categorie, rec.ca_total;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

5. Cas d'application en production : Traitement de partitions par dates#

Dans un Data Warehouse, vous devez souvent recalculer les 7 derniers jours d'historique pour intégrer les données en retard (latence de reporting).

Recalcul partitionné sur 7 jours glissants
DO
$$
DECLARE
    v_date_traitement DATE;
    v_query TEXT;
BEGIN
    -- Boucle sur les 7 derniers jours
    FOR decallage IN 1..7 LOOP
        v_date_traitement := CURRENT_DATE - decallage;

        RAISE NOTICE 'Traitement de la partition date : %', v_date_traitement;

        -- Construction d'une requête dynamique sécurisée
        v_query := format(
            'DELETE FROM dwh.fct_daily_snapshot WHERE date_snapshot = %L; ' ||
            'INSERT INTO dwh.fct_daily_snapshot (date_snapshot, total_commandes) ' ||
            'SELECT date_cmd, count(*) FROM staging.commandes WHERE date_cmd = %L GROUP BY date_cmd;',
            v_date_traitement, v_date_traitement
        );

        -- Exécution de la commande SQL dynamique
        EXECUTE v_query;
    END LOOP;

    RAISE NOTICE 'Recalcul des 7 partitions achevé avec succès !';
END;
$$ LANGUAGE plpgsql;

6. Mise en pratique : Audit de volumétrie des tables d'un schéma#

Objectif#

Écrire un bloc anonyme qui inspecte automatiquement toutes les tables présentes dans le schéma public via la vue système information_schema.tables, compte dynamiquement le nombre de lignes de chacune à l'aide d'une requête EXECUTE ... INTO, et alerte si une table contient plus de 10 000 lignes.

Solution de l'exercice d'audit
DO
$$
DECLARE
    rec RECORD;
    v_nb_lignes BIGINT;
    v_sql TEXT;
BEGIN
    RAISE NOTICE '=========== AUDIT DE VOLUMÉTRIE DU SCHÉMA PUBLIC ===========';

    FOR rec IN
        SELECT table_name
        FROM information_schema.tables
        WHERE table_schema = 'public'
          AND table_type = 'BASE TABLE'
        ORDER BY table_name
    LOOP
        -- Construction de la requête de comptage dynamique
        v_sql := format('SELECT COUNT(*) FROM public.%I', rec.table_name);

        -- Exécution et capture du résultat
        EXECUTE v_sql INTO v_nb_lignes;

        IF v_nb_lignes > 10000 THEN
            RAISE WARNING 'Table [ % ] : % lignes (SEUIL DÉPASSÉ)', rec.table_name, v_nb_lignes;
        ELSE
            RAISE NOTICE 'Table [ % ] : % lignes', rec.table_name, v_nb_lignes;
        END IF;
    END LOOP;

    RAISE NOTICE '=============================================================';
END;
$$ LANGUAGE plpgsql;

Vous maîtrisez désormais les variables, les conditions et les boucles. Dans la prochaine et dernière leçon de programmation, nous encapsulerons toutes ces briques dans des sous-programmes réutilisables : les Fonctions et les Procédures Stockées.

Commentaires

Inscrivez-vous à une formation complète

Voir le catalogue