Skip to content

Loops — Répétition de tâches

Les loops permettent d'exécuter une même tâche plusieurs fois avec des paramètres différents — sur une liste statique ou sur le résultat d'une requête SQL.

Boucle statique — loop:

La tâche s'exécute une fois par entrée de la liste. Chaque entrée est un dictionnaire de paramètres injectés dans le SQL.

nodes:
  rapport_region:
    sql: sql/rapport_region.sql
    target: "{{ params.schema }}.rapport_{{ params.region }}"
    output_write: replace
    loop:
      - { region: nord,  code: "59" }
      - { region: sud,   code: "13" }
      - { region: ouest, code: "44" }
-- sql/rapport_region.sql
SELECT
    '{{ params.region }}'             AS region,
    COUNT(*)                          AS nb_clients
FROM {{ params.schema }}.clients
WHERE departement = '{{ params.code }}'

Résultat : 3 tables créées — rapport_nord, rapport_sud, rapport_ouest.


Boucle dynamique — loop_from_query:

La liste d'itérations est générée par une requête SQL. Chaque ligne de résultat devient un jeu de paramètres pour une itération.

nodes:
  rapport_par_region:
    sql: sql/rapport_region.sql
    target: "{{ params.schema }}.rapport_{{ params.code_region }}"
    output_write: replace
    loop_from_query: >
      SELECT code_region, libelle
      FROM {{ params.schema }}.ref_regions
      WHERE actif = TRUE

La requête retourne par exemple :

code_region libelle
IDF Île-de-France
ARA Auvergne-Rhône-Alpes

Chaque ligne devient une itération avec {{ params.code_region }} et {{ params.libelle }} disponibles dans le SQL.


Contexte Jinja2 dans la boucle

Dans chaque itération, le contexte loop est disponible :

Variable Type Description
{{ loop.index }} int Numéro d'itération (commence à 1)
{{ loop.index0 }} int Numéro d'itération (commence à 0)
{{ loop.first }} bool true si c'est la première itération
{{ loop.last }} bool true si c'est la dernière itération
{{ loop.length }} int Nombre total d'itérations
-- Exemple : ajouter un commentaire SQL par itération
-- Itération {{ loop.index }} / {{ loop.length }} — région {{ params.libelle }}
SELECT ...
{% if loop.first %}
-- Première région traitée
{% endif %}

Options de la boucle

loop_limit — limiter le nombre d'itérations

Utile pour tester sans traiter toute la liste.

loop_from_query: "SELECT code FROM ref_regions"
loop_limit: 3    # traite seulement les 3 premières

loop_parallel — exécution parallèle

Par défaut les itérations sont séquentielles. Activer le parallélisme pour gagner du temps.

loop_parallel: true
loop_max_workers: 4    # nombre de threads (défaut: 4)

Attention

En mode parallèle, les itérations ne doivent pas écrire dans la même table. Utilise une cible dynamique : target: "schema.table_{{ params.region }}".

loop_on_error — comportement sur erreur

Valeur Comportement
abort (défaut) Arrête la tâche entière dès la première itération en échec
skip Log l'erreur et passe à l'itération suivante, tâche = KO
continue Log l'erreur et continue, tâche = WARNING (non bloquante)
loop_on_error: skip    # ignorer les régions en erreur, continuer les autres

loop_pre_hook / loop_post_hook — hooks par itération

Exécutés avant et après chaque itération (contrairement à pre_hook/post_hook qui s'exécutent une seule fois pour la tâche).

loop_pre_hook:
  file: sql/hooks/log_debut_iteration.sql
  soft_fail: true
loop_post_hook:
  file: sql/hooks/log_fin_iteration.sql
  soft_fail: true

Exemple complet

nodes:
  export_par_pays:
    sql: sql/export_pays.sql
    target: "{{ params.schema }}.export_{{ params.iso_pays }}"
    output_write: replace
    description: "Export mensuel par pays"
    tags: [monthly]

    # Source dynamique de la liste
    loop_from_query: >
      SELECT iso_pays, nom_pays
      FROM {{ params.schema }}.ref_countries
      WHERE actif = TRUE
      ORDER BY iso_pays

    # Traiter 4 pays en parallèle
    loop_parallel: true
    loop_max_workers: 4

    # Passer les pays en erreur, ne pas bloquer les autres
    loop_on_error: skip

    # Maximum 10 pays (pour les tests)
    loop_limit: 10

    # Log avant chaque pays
    loop_pre_hook:
      file: sql/hooks/log_debut.sql
      soft_fail: true
-- sql/export_pays.sql
-- Traitement {{ loop.index }}/{{ loop.length }} — {{ params.nom_pays }}
SELECT
    '{{ params.iso_pays }}'                    AS pays,
    '{{ params.nom_pays }}'                    AS libelle,
    {{ date_trunc('month', 'date_commande') }} AS mois,
    COUNT(*)                                   AS nb_commandes,
    ROUND(SUM(montant), 2)                    AS ca_total
FROM {{ params.schema }}.commandes
WHERE pays = '{{ params.iso_pays }}'
GROUP BY
    pays, libelle,
    {{ date_trunc('month', 'date_commande') }}
ORDER BY mois DESC