03 — dbt et modélisation
dbt est le terrain de jeu idéal pour l'IA : tout y est du texte versionné, SQL et YAML. Mais la convention de nommage, c'est toi qui la donnes.
Temps de lecture : 18 min | Niveau : Intermédiaire
Ce que tu sauras faire après
- Faire générer un modèle dbt conforme à la convention de nommage officielle.
- Faire écrire les fichiers YAML de documentation et de tests sans clé inventée.
- Faire relire une couche de modélisation entière et en sortir une liste d'actions.
- Faire expliquer un graphe de dépendances (lineage) à quelqu'un qui n'est pas technique.
- Reconnaître les points où la syntaxe dbt a changé récemment, pour ne pas te faire piéger.
1. Pourquoi dbt et l'IA vont bien ensemble
Un projet dbt, c'est trois types de fichiers :
- des
.sqlqui contiennent unselect; - des
.ymlqui décrivent les modèles, les colonnes, les tests ; - des
.mdqui portent les blocs de documentation.
Tout est du texte, tout est dans Git. Un modèle de langage travaille bien là-dessus à condition que tu lui donnes tes conventions. Sans elles, il produira du dbt générique, correct sur le fond, incohérent avec ton projet.
2. La convention de nommage : à mettre dans chaque prompt
dbt Labs publie une convention dans son guide « How we structure our dbt projects ». Pour la couche de préparation (staging), le motif recommandé est :
stg_[source]__[entity]s.sqlExemples donnés par la doc : stg_ecom__orders.sql, et en projet mono-source stg_orders.sql, stg_customers.sql. Le double tiret bas __ sépare le système source de l'entité.
Pourquoi ce double tiret bas ? La doc l'explique par un cas concret : google_analytics__campaigns se lit sans ambiguïté, alors que google_analytics_campaigns peut se lire « campaigns venant de la source google_analytics » ou « analytics_campaigns venant de la source google ».
À éviter, toujours selon la doc : stg_[entity].sql sans le nom de la source. Le texte officiel :
«
stg_[entity].sql- might be specific enough at first for a single-source project, but will break down in time as you add sources. Adding the source system into the file name aids in discoverability, and allows understanding where a component model came from even if you aren't looking at the file tree. »
Deux autres règles fortes de cette couche :
- Une relation un pour un : un modèle de staging par table source.
- Matérialisation en vue : la doc recommande
+materialized: view, pour que les modèles en aval reçoivent toujours des données fraîches et pour ne pas consommer d'espace.
Ce qu'on fait en staging, d'après la doc. Quatre gestes, pas un de plus :
- Renommer (renaming) :
subtotaldevientmontant_ht. - Caster les types (type casting) : une date stockée en texte devient une vraie date.
- Des calculs de base (basic computations) : passer des centimes aux euros.
- Catégoriser (categorizing) : une logique conditionnelle qui regroupe des valeurs en catégories ou en booléens.
Ce qu'on ne fait pas en staging : des jointures, et des agrégations. Elles changeraient la granularité de la table.
Enfin, une règle structurante, que la doc énonce en une phrase :
« Staging models are the only place we'll use the
sourcemacro, and our staging models should have a 1-to-1 relationship to our source tables. »
Traduction : la macro source() ne s'utilise que en staging, et il y a un modèle de staging par table source, pas deux, pas zéro.
# dbt_project.yml
models:
jaffle_shop:
staging:
+materialized: view3. Générer un modèle dbt
AVANT
Écris-moi un modèle dbt pour les commandes.Tu obtiens un select * from orders, sans ref(), sans source(), sans CTE, avec un nom de fichier au hasard.
APRÈS
Génère un modèle de staging dbt.
Source déclarée :
- source : "ecom", table : "raw_commandes"
Colonnes brutes (nom, type, sens) :
- id (string) : identifiant de la commande
- store_id (string) : code magasin sur 2 caractères
- customer (string) : identifiant client
- subtotal (integer) : montant HT en centimes
- ordered_at (timestamp) : horodatage de la commande, en UTC
- status (string) : 'placed' | 'shipped' | 'completed' | 'returned'
Conventions du projet, à respecter strictement :
- Nom du fichier : stg_[source]__[entite]s.sql
- Structure : une CTE "source", une CTE "renamed", puis select final.
- Groupes de colonnes commentés dans cet ordre : ids, chaînes, numériques,
booléens, horodatages.
- Suffixe _id pour toutes les clés.
- Les montants en centimes sont convertis en euros et renommés sans suffixe.
- Matérialisation : view.
- Aucune jointure, aucune agrégation dans cette couche.
Rends le fichier .sql, puis le fichier .yml correspondant.Le modèle produit alors quelque chose de la forme recommandée par dbt Labs :
-- models/staging/ecom/stg_ecom__commandes.sql
with source as (
select * from {{ source('ecom', 'raw_commandes') }}
),
renamed as (
select
---------- ids
id as commande_id,
store_id as magasin_id,
customer as client_id,
---------- numeriques
subtotal as montant_ht_centimes,
subtotal / 100.0 as montant_ht,
---------- chaines
status as statut,
---------- horodatages
ordered_at as commande_at
from source
)
select * from renamedPourquoi c'est mieux : le modèle a la liste réelle des colonnes, leur sens, la convention de nommage, l'ordre des groupes, et l'interdiction de joindre. Il n'a plus rien à inventer.
4. Les fichiers YAML : documentation et tests
Documentation
La doc dbt montre la structure de base :
models:
- name: events
description: This table contains clickstream events from the marketing website
columns:
- name: event_id
description: This is a unique identifier for the eventPour les descriptions longues ou réutilisées, dbt propose les blocs docs, écrits dans un fichier .md avec la balise Jinja docs :
{% docs table_events %}
Cette table contient les événements de navigation du site marketing.
Une ligne = un événement. Granularité : event_id.
{% enddocs %}On y fait référence avec la fonction doc() :
description: '{{ doc("table_events") }}'Deux commandes complètent le tout :
dbt docs generate: compile le projet et écrit le site statique.dbt docs serve: ouvre ce site en local pour le consulter.
Point de version à vérifier chez toi. La page de documentation dbt consultée indique que
dbt docs generate« is available for dbt v2.0 and later » et produit des artefacts Parquet v2. Si ton projet est sur une version antérieure, vérifie le comportement exact dans la doc correspondant à ta version avant d'automatiser quoi que ce soit.
Faire pousser les descriptions jusque dans l'entrepôt
persist_docs recopie tes descriptions dbt en commentaires de table et de colonne directement dans la base :
# dbt_project.yml
models:
mon_projet:
+persist_docs:
relation: true
columns: trueou en configuration au niveau du modèle :
{{ config(persist_docs={"relation": true, "columns": true}) }}La doc liste comme adaptateurs supportant les deux niveaux : Postgres, Redshift, Snowflake, BigQuery, Databricks, Apache Spark et Starburst Galaxy (dbt-trino). Deux limites notées : sur Databricks, les commentaires de colonne exigent file_format: delta ; et les sources ne supportent pas persist_docs.
C'est un point de gouvernance majeur, on y revient dans la fiche 07-documentation-et-gouvernance.
Tests
Les quatre tests génériques livrés avec dbt sont unique, not_null, accepted_values et relationships. La clé YAML actuelle est data_tests: ; l'ancienne clé tests: reste supportée, mais tu ne peux pas utiliser les deux sur la même ressource.
models:
- name: stg_ecom__commandes
description: "Une ligne par commande passée sur le site e-commerce."
columns:
- name: commande_id
description: "Identifiant unique de la commande."
data_tests:
- unique
- not_null
- name: statut
description: "Statut de la commande au moment du chargement."
data_tests:
- accepted_values:
arguments:
values: ['placed', 'shipped', 'completed', 'returned']
- name: client_id
data_tests:
- relationships:
arguments:
to: ref('stg_ecom__clients')
field: client_idPoint de version, encore. Le bloc
arguments:sous un test paramétré est la forme documentée à partir de dbt v1.10.5, et la doc indique qu'il devient obligatoire en dbt v2 ; l'ancienne écriture avec les propriétés au premier niveau est dépréciée. Vérifie ta version avant de copier-coller, et précise-la dans ton prompt.
La recommandation officielle est simple et vaut d'être suivie :
« We recommend that every model has a data test on a primary key, that is, a column that is
uniqueandnot_null. »
5. Relire une couche de modélisation
C'est un usage sous-estimé. Tu colles tes dix modèles .sql et leurs .yml, et tu demandes une revue. Le modèle voit tout le corpus d'un coup, ce qu'un humain fait rarement.
Ce qu'il faut demander explicitement, sinon tu auras des généralités :
- Incohérences de nommage entre modèles (une colonne appelée
client_idici etid_clientlà). - Granularité déclarée contre granularité réelle : la description dit « une ligne par commande », mais une jointure duplique.
- Modèles sans test de clé primaire.
- Logique dupliquée entre deux modèles, candidate à une macro.
- Jointures ou agrégations présentes en staging, contraires à la convention.
6. Expliquer un graphe de dépendances (lineage)
Le lineage dbt est construit automatiquement : dès que tu écris {{ ref('mon_modele') }} ou {{ source('ecom', 'raw_commandes') }}, dbt enregistre la dépendance dans le graphe. La doc le montre sur les sources :
select ...
from {{ source('jaffle_shop', 'orders') }}
left join {{ source('jaffle_shop', 'customers') }} using (customer_id)qui se compile en :
from raw.jaffle_shop.orders
left join raw.jaffle_shop.customers using (customer_id)Quand un métier te demande « d'où vient ce chiffre ? », l'IA sert à traduire le graphe en phrases. Tu lui donnes la chaîne de ref() et tu demandes un récit, source par source, sans terme technique.
🧰 Templates à copier-coller
Template A — Générer un modèle dbt
Tu es analytics engineer senior, expert dbt.
Version de dbt : [VERSION_EXACTE]
Entrepôt : [BIGQUERY_SNOWFLAKE_POSTGRES_DATABRICKS]
Couche cible : [staging | intermediate | marts]
Source :
- source() : ('[NOM_SOURCE]', '[NOM_TABLE]') ← si couche staging
- ref() : [LISTE_DES_MODELES_AMONT] ← si couche intermediate/marts
Colonnes disponibles (nom | type | sens métier) :
[COLLE_LA_LISTE_COMPLETE]
Conventions du projet, à respecter strictement :
- Nom de fichier : [CONVENTION_EX_stg_[source]__[entite]s.sql]
- Structure : [EX_CTE_source_PUIS_CTE_renamed_PUIS_select_final]
- Groupes de colonnes commentés dans l'ordre : [EX_ids_chaines_numeriques_booleens_horodatages]
- Suffixes imposés : [EX_ _id POUR LES CLES, _at POUR LES HORODATAGES]
- Matérialisation : [view | table | incremental]
- Interdits dans cette couche : [EX_JOINTURES_ET_AGREGATIONS]
Rends, dans cet ordre :
1. le fichier .sql complet ;
2. le fichier .yml avec description du modèle, description de chaque colonne,
et data_tests unique + not_null sur la clé primaire ;
3. <a_confirmer> : toute hypothèse que tu as dû faire sur le sens d'une colonne.
N'invente aucun nom de colonne absent de la liste fournie.Template B — Écrire le YAML de doc et de tests
Version de dbt : [VERSION_EXACTE]
<modele_sql>
[COLLE_LE_SQL_DU_MODELE]
</modele_sql>
<contexte_metier>
Ce modèle sert à : [USAGE]
Granularité : une ligne par [GRANULARITE]
Propriétaire : [EQUIPE_OU_PERSONNE]
Fréquence de rafraîchissement : [FREQUENCE]
</contexte_metier>
Écris le fichier .yml :
- description du modèle : 2 phrases, dont la granularité explicite ;
- description de CHAQUE colonne, en français, sans paraphraser le nom
(interdit : "client_id : l'identifiant du client") ;
- data_tests : unique + not_null sur la clé primaire,
accepted_values sur les colonnes d'énumération,
relationships sur chaque clé étrangère ;
- utilise la clé `data_tests:` et le bloc `arguments:` pour les tests paramétrés,
en cohérence avec la version de dbt indiquée ci-dessus.
Pour toute colonne dont tu ne peux pas déduire le sens depuis le SQL,
écris "description: TODO — sens à confirmer" au lieu d'inventer.Template C — Revue d'une couche de modélisation
Voici [NOMBRE] modèles de la couche [COUCHE] d'un projet dbt.
<modeles>
[COLLE_CHAQUE_FICHIER_SQL_ET_YML_PRECEDE_DE_SON_CHEMIN]
</modeles>
<conventions>
[COLLE_TES_CONVENTIONS_DE_NOMMAGE_ET_DE_STRUCTURE]
</conventions>
Fais une revue et rends un tableau :
| fichier | problème | gravité (bloquant / à corriger / cosmétique) | correction proposée |
Cherche en priorité :
1. incohérences de nommage entre modèles ;
2. écart entre la granularité déclarée dans la description et celle du SQL ;
3. modèles sans test unique + not_null sur leur clé primaire ;
4. logique dupliquée entre deux modèles ;
5. jointures ou agrégations présentes dans une couche qui les interdit ;
6. colonnes sans description, ou avec une description qui paraphrase le nom.
Classe par gravité décroissante. Maximum 15 lignes : garde les plus importantes.Template D — Expliquer un lineage à un non-technicien
<chaine_de_modeles>
[COLLE_LES_MODELES_DE_LA_SOURCE_JUSQU_AU_MODELE_FINAL]
</chaine_de_modeles>
Explique à [PROFIL_EX_UN_CONTROLEUR_DE_GESTION] d'où vient l'indicateur
"[NOM_INDICATEUR]".
Format :
- un récit en étapes numérotées, de la source jusqu'à l'indicateur final ;
- une phrase par étape, sans terme technique (pas de "CTE", pas de "jointure",
pas de "ref") ;
- à chaque étape, indique ce qui est FILTRÉ ou EXCLU, car c'est ce qui
explique les écarts de chiffres ;
- termine par un encadré "Ce que ce chiffre NE contient PAS".
Si une exclusion n'est pas lisible dans le code fourni, ne l'invente pas :
écris "exclusion possible non visible ici, à vérifier".A retenir
- Mets la convention de nommage dans le prompt :
stg_[source]__[entity]s.sql, groupes de colonnes, suffixes. - En staging : une relation un pour un avec la source, matérialisation en vue, pas de jointure ni d'agrégation, et c'est le seul endroit où on utilise
source(). - YAML : clé
data_tests:(l'anciennetests:reste tolérée mais pas les deux ensemble), tests paramétrés sousarguments:. - Chaque modèle mérite
unique+not_nullsur sa clé primaire — c'est la recommandation officielle. persist_docspousse tes descriptions jusque dans les commentaires de l'entrepôt : c'est le pont entre dbt et ta gouvernance.- Les détails de syntaxe dbt ont bougé récemment (
arguments:,dbt docs generateen v2) : indique toujours ta version exacte dans le prompt.