# 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. - Published: 2026-05-25 - Updated: 2026-05-27 - Author: SharpSkill - Tags: dbt, data-engineering, data-transformation, testing, interview, analytics-engineering, sql - Reading time: 10 min --- 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. > **Ce que fait réellement dbt** > > 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](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview), 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. ```sql -- 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 NULL ``` Les 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. ```sql -- 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_name ``` Les **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. ```sql -- 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](https://docs.getdbt.com/docs/build/materializations) 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` : ```sql -- 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. ## 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 : ```yaml # 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_id ``` Les tests singuliers valident des règles métier que les tests génériques ne peuvent pas exprimer : ```sql -- 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 < 0 ``` Les **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](https://docs.getdbt.com/docs/dbt-versions/cloud-release-tracks), 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](https://docs.getdbt.com/docs/build/jinja-macros). Les macros sont des fonctions Jinja stockées dans le répertoire `macros/`. ```sql -- 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 : ```sql -- 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](/technologies/data-engineering). **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](/blog/data-engineering/data-engineering-interview-questions-2026), incluant des sujets comme l'[architecture des pipelines ETL vs ELT](/blog/data-engineering/etl-vs-elt-data-pipeline-architecture), les modules de pratique SharpSkill sur les [fondamentaux dbt](/technologies/data-engineering/interview-questions/dbt-fundamentals-de) et les [patterns dbt avancés](/technologies/data-engineering/interview-questions/dbt-advanced-de) couvrent ces concepts avec des exercices interactifs. ## 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 filtre `is_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 `--sample` pour 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/data-engineering/dbt-data-transformations-testing-interview-2026