dbt en 2026 : transformations de données, tests et questions d'entretien
Tutoriel pratique sur dbt (data build tool) : transformations SQL, modélisation par couches, stratégies de test et vraies questions d'entretien pour les postes de data engineering en 2026.

dbt (data build tool) s'est imposé comme le framework standard pour transformer les données dans les entrepôts modernes, utilisé en production par plus de 40 000 entreprises en 2026. Ce tutoriel couvre les mécanismes des transformations dbt, les stratégies de test et les questions qui reviennent réellement lors des entretiens de data engineering.
dbt gère le T de l'ELT. Il compile des instructions SQL SELECT en DDL (CREATE TABLE, CREATE VIEW, MERGE) et les exécute sur un entrepôt — Snowflake, BigQuery, Redshift ou Databricks. Le versioning, la résolution des dépendances, les tests et la documentation sont intégrés.
Modélisation par couches : staging, intermédiaire et marts
Un projet dbt bien structuré sépare les transformations en trois couches. Cette approche, popularisée par la communauté dbt, impose des modèles à responsabilité unique et facilite le débogage.
Les modèles de staging sont les plus proches des données brutes. Leur rôle est restreint : renommer les colonnes, convertir les types et filtrer les lignes parasites. Aucune jointure, aucune agrégation.
-- models/staging/stg_orders.sql
WITH source AS (
SELECT * FROM {{ source('ecommerce', 'raw_orders') }}
)
SELECT
id AS order_id,
customer_id,
CAST(order_date AS DATE) AS order_date,
CAST(amount AS DECIMAL(10, 2)) AS order_amount,
LOWER(status) AS order_status
FROM source
WHERE id IS NOT NULLLes modèles intermédiaires appliquent la logique métier — jointures, agrégations, fonctions de fenêtrage. Ils référencent les modèles de staging via ref(), qui enregistre les dépendances dans le DAG.
-- models/intermediate/int_customer_orders.sql
WITH orders AS (
SELECT * FROM {{ ref('stg_orders') }}
),
customers AS (
SELECT * FROM {{ ref('stg_customers') }}
)
SELECT
c.customer_id,
c.customer_name,
COUNT(o.order_id) AS total_orders,
SUM(o.order_amount) AS lifetime_value,
MIN(o.order_date) AS first_order_date,
MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_nameLes marts constituent la couche finale de consommation — des tables propres et documentées que les tableaux de bord et les analystes interrogent directement.
Cette approche par couches signifie qu'une source défaillante n'affecte que le staging, pas l'ensemble du pipeline. Chaque couche peut être testée indépendamment.
La fonction ref() et la résolution du DAG
ref() n'est pas un alias. Appeler {{ ref('stg_orders') }} accomplit deux choses : la fonction résout le nom de table pleinement qualifié au moment de la compilation et enregistre une arête de dépendance dans le graphe orienté acyclique (DAG) de dbt. Sans ref(), dbt ne peut pas déterminer l'ordre d'exécution.
Une erreur courante consiste à coder en dur les noms de table au lieu d'utiliser ref(). Cela casse le suivi des dépendances et peut entraîner l'exécution de modèles avant que leurs dépendances en amont ne soient prêtes.
-- Mauvais : référence codée en dur, aucun suivi de dépendance
SELECT * FROM analytics.stg_orders
-- Bon : ref() enregistre la dépendance
SELECT * FROM {{ ref('stg_orders') }}Le DAG alimente aussi des fonctionnalités comme dbt run --select stg_orders+, qui exécute un modèle et tout ce qui se trouve en aval — utile pour des reconstructions ciblées après un changement de schéma source.
Matérialisations : choisir la bonne stratégie de stockage
dbt propose quatre matérialisations intégrées, chacune adaptée à des schémas d'accès et des volumes de données différents.
| Matérialisation | Stockage | Idéal pour | Compromis |
|----------------|---------|----------|----------|
| view | Aucun stockage (calculé à la lecture) | Transformations légères, petits jeux de données | Lectures lentes sur de gros volumes |
| table | Reconstruction complète à chaque exécution | Modèles de la couche mart, lectures rapides | Reconstruit tout, coût plus élevé |
| incremental | N'ajoute/fusionne que les nouvelles lignes | Grandes tables de faits, flux d'événements | Logique plus complexe, nécessite unique_key |
| ephemeral | Inséré comme CTE, jamais matérialisé | Logique réutilisable partagée entre modèles | Non interrogeable directement |
La valeur par défaut est view. Elle se remplace dans le bloc de config du modèle ou dans dbt_project.yml :
-- models/marts/fct_daily_revenue.sql
{{
config(
materialized='incremental',
unique_key='revenue_date',
incremental_strategy='merge'
)
}}
SELECT
DATE(order_date) AS revenue_date,
SUM(order_amount) AS daily_revenue,
COUNT(DISTINCT customer_id) AS unique_customers
FROM {{ ref('stg_orders') }}
WHERE order_status = 'completed'
{% if is_incremental() %}
AND order_date > (SELECT MAX(revenue_date) FROM {{ this }})
{% endif %}
GROUP BY DATE(order_date)Le bloc is_incremental() ne s'exécute que lors des builds incrémentaux — lors d'un rafraîchissement complet (dbt run --full-refresh), il est ignoré et la table entière est reconstruite.
Prêt à réussir tes entretiens Data Engineering ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Tester la qualité des données à chaque couche
dbt fournit deux catégories de tests : les tests génériques déclarés en YAML et les tests singuliers écrits sous forme de fichiers SQL autonomes qui renvoient des lignes lorsqu'ils échouent.
Les tests génériques couvrent les contraintes structurelles :
# models/staging/_stg_models.yml
version: 2
models:
- name: stg_orders
columns:
- name: order_id
tests:
- unique # Aucun ID de commande en double
- not_null # Chaque ligne a un ID de commande
- name: order_status
tests:
- accepted_values:
values: ['completed', 'pending', 'cancelled', 'refunded']
- name: customer_id
tests:
- not_null
- relationships: # Vérification d'intégrité référentielle
to: ref('stg_customers')
field: customer_idLes tests singuliers valident des règles métier que les tests génériques ne peuvent pas exprimer :
-- tests/assert_revenue_never_negative.sql
-- Renvoie les lignes où le revenu quotidien est négatif (devrait renvoyer 0 ligne)
SELECT
revenue_date,
daily_revenue
FROM {{ ref('fct_daily_revenue') }}
WHERE daily_revenue < 0Les tests unitaires, introduits dans dbt 1.8, valident la logique de transformation avec des entrées contrôlées et des sorties attendues — sans entrepôt requis. Ils s'exécutent directement durant la phase de test de dbt.
La stratégie de test devrait suivre le flux de données : contrôles de fraîcheur et validations du nombre de lignes au niveau source, tests de schéma au niveau staging (not_null, unique), intégrité référentielle au niveau intermédiaire et assertions de logique métier au niveau mart.
dbt Core v1.10 et le moteur Fusion
dbt Core v1.10 a introduit le drapeau --sample pour les commandes run et build. Ce drapeau applique un échantillonnage temporel à ref() et source(), permettant aux développeurs de valider les transformations sur un sous-ensemble de données sans le coût d'un build complet. Pratique pour itérer sur des modèles adossés à des tables de faits de plusieurs milliards de lignes.
Le moteur dbt Fusion, une réécriture complète en Rust, est désormais la valeur par défaut pour les nouveaux projets dans dbt Cloud sur Snowflake, BigQuery, Redshift et Databricks. Fusion introduit le versioning sémantique à partir de la 2.0 et apporte des améliorations de performance notables à la compilation et à l'exécution.
Autres ajouts notables de 2026 : la spécification YAML du Semantic Layer pour des définitions de métriques centralisées, les Cost Insights (bêta) pour estimer le calcul d'entrepôt par modèle, et les packages privés natifs désormais en disponibilité générale.
Macros et Jinja pour une logique réutilisable
Lorsque le même motif SQL apparaît dans plusieurs modèles, il faut l'extraire dans une macro. Les macros sont des fonctions Jinja stockées dans le répertoire macros/.
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name) %}
ROUND(CAST({{ column_name }} AS DECIMAL(10, 4)) / 100, 2)
{% endmacro %}Appelée dans n'importe quel modèle :
-- models/staging/stg_payments.sql
SELECT
payment_id,
order_id,
{{ cents_to_dollars('amount_cents') }} AS amount_dollars,
payment_method
FROM {{ source('stripe', 'payments') }}Cela garde la base de code DRY. L'alternative — copier la formule de conversion dans chaque modèle qui en a besoin — crée une charge de maintenance et des incohérences.
Questions d'entretien réellement posées
Les entretiens de data engineering en 2026 testent de plus en plus la connaissance de dbt au-delà des bases. Voici les questions qui différencient les candidats, tirées de schémas observés dans divers postes au sein d'entreprises utilisant des stacks de données modernes.
Q : Un modèle incrémental a retraité toute la table pendant la nuit. Que s'est-il passé ?
La cause la plus fréquente : la colonne unique_key contenait des valeurs nulles. Lorsque dbt tente un MERGE sur une clé nulle, la correspondance échoue pour chaque ligne et il insère donc des doublons. Deuxième cause : quelqu'un a exécuté dbt run --full-refresh sans réaliser que cela reconstruit la table de zéro. Troisième : le filtre is_incremental() référençait une colonne qui n'existe pas encore dans la table cible (premier run vs runs suivants). Le débogage commence par l'inspection du SQL compilé dans target/compiled/.
Q : Comment les tests devraient-ils être structurés en couches dans un projet dbt de production ?
Les contrôles de fraîcheur source s'exécutent en premier — si la source n'a pas été mise à jour, les modèles en aval ne devraient pas s'exécuter sur des données obsolètes. Les tests de staging valident les contraintes de schéma : clés primaires (unique + not_null), valeurs acceptées et conversions de types. Les tests intermédiaires vérifient l'intégrité référentielle à travers les jointures. Les tests de mart affirment les invariants métier : revenu >= 0, utilisateurs actifs >= utilisateurs payants, et somme des parties égale au total. Tous les tests s'exécutent en CI sur les pull requests contre un schéma de dev, puis à nouveau après déploiement en production avec alerting.
Q : Quand un modèle devrait-il être éphémère plutôt qu'une vue ?
Les modèles éphémères insèrent leur SQL comme CTE dans les modèles consommateurs — utile pour une logique réutilisable qui n'a pas besoin d'être interrogée indépendamment. Les vues sont calculées à la lecture et existent comme objets interrogeables. Le compromis : les modèles éphémères ne peuvent pas être testés directement (ils n'ont aucune table sur laquelle exécuter des assertions) et ne peuvent pas apparaître dans les outils de lignage de données. Utiliser l'éphémère pour la logique d'aide interne ; utiliser les vues pour tout ce qui nécessite des tests ou un monitoring indépendants.
Q : Expliquer la différence entre source() et ref().
source() pointe vers des tables brutes définies dans un fichier sources.yml — ce sont des tables que dbt ne gère pas. ref() pointe vers d'autres modèles dbt. Les deux enregistrent des dépendances dans le DAG, mais source() active aussi les contrôles de fraîcheur (dbt source freshness), ce que ref() ne fait pas. Utiliser source() pour les tables brutes et ref() pour tout le reste n'est pas optionnel — c'est ainsi que dbt sait ce qu'il contrôle et ce qu'il ne contrôle pas.
Pour un ensemble plus large de questions d'entretien en data engineering, incluant des sujets comme l'architecture des pipelines ETL vs ELT, les modules de pratique SharpSkill sur les fondamentaux dbt et les patterns dbt avancés couvrent ces concepts avec des exercices interactifs.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Conclusion
- dbt transforme les données brutes de l'entrepôt via des instructions SQL SELECT, compilées automatiquement en DDL — la structure par couches staging/intermédiaire/mart garde chaque modèle concentré sur une responsabilité unique
- La fonction
ref()est l'épine dorsale de la gestion des dépendances : elle résout les noms de table et construit le DAG qui contrôle l'ordre d'exécution - Les matérialisations incrémentales réduisent le coût sur les grandes tables, mais exigent une gestion soignée de
unique_key, des valeurs nulles et du filtreis_incremental() - Les tests devraient suivre le flux de données — fraîcheur source, contraintes de schéma au staging, intégrité référentielle à l'intermédiaire, assertions de logique métier au niveau mart
- dbt Core v1.10 apporte le drapeau
--samplepour une itération plus rapide, tandis que le moteur Fusion (basé sur Rust, version 2.0) est désormais la valeur par défaut dans dbt Cloud - Les questions d'entretien sur dbt portent sur le débogage de pannes réelles (retraitement incrémental, clés nulles, sources obsolètes) plutôt que sur des définitions — l'expérience pratique du SQL compilé et des pipelines CI compte plus que les réponses apprises par cœur
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Top 25 Questions d'Entretien Data Engineering en 2026
Les questions d'entretien data engineering les plus fréquentes en 2026 : SQL avancé, pipelines temps réel, architecture lakehouse, Spark, Airflow et optimisation des coûts cloud.

ETL vs ELT en 2026 : Guide Complet de l'Architecture des Pipelines de Données
Guide technique comparant les architectures ETL et ELT pour les pipelines de données en 2026. Inclut des exemples de code Python et dbt, des tableaux comparatifs de coûts, et des recommandations pratiques pour choisir la bonne approche.

Apache Kafka pour les Data Engineers : Architecture KRaft, Partitions et Pipelines Exactly-Once
Guide approfondi sur Apache Kafka pour les data engineers. Architecture KRaft sans ZooKeeper, strategies de partitionnement, consumer groups, CDC avec Debezium, transactions exactly-once et Share Groups, avec exemples de code Python et questions d'entretien.