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_parallel — exécution parallèle
Par défaut les itérations sont séquentielles. Activer le parallélisme pour gagner du temps.
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_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