Tout outil de transformation SQL fait face au même problème : le SQL pur ne suffit pas. Vous avez besoin de variables, de conditions, de boucles et de logique réutilisable. La solution, c’est le templating — une couche qui génère du SQL à partir de quelque chose de plus expressif.
dbt a choisi Jinja, un moteur de templating Python. Dataform a choisi JavaScript. Les deux fonctionnent. Les deux ont des compromis (pour une comparaison plus large entre Dataform et dbt, consultez le guide dédié). Le workflow de votre équipe et ses compétences existantes feront davantage la différence que le SQL compilé.
Deux philosophies, un seul objectif
Jinja vient du monde des frameworks web Python. Il a été conçu pour générer du HTML, mais fonctionne tout aussi bien pour le SQL. La syntaxe utilise des doubles accolades {{ }} pour les expressions et {% %} pour la logique. Quiconque a utilisé Flask, Django ou Ansible le reconnaîtra immédiatement.
Le SQLX de Dataform utilise les template literals JavaScript avec l’interpolation ${}. Vos fichiers SQL peuvent inclure un bloc config et des expressions JavaScript inline. Pour une logique plus complexe, vous écrivez des fichiers .js classiques qui définissent les modèles de manière programmatique.
Jinja traite le templating comme une préoccupation distincte de la programmation. JavaScript traite le templating comme une chose de plus que JavaScript sait faire.
La syntaxe en un coup d’œil
La même opération dans les deux langages :
Référencer un autre modèle :
-- dbt (Jinja)SELECT customer_id, email FROM {{ ref('base__stripe__customers') }}
-- Dataform (JavaScript)SELECT customer_id, email FROM ${ref("base__stripe__customers")}Accéder à une variable :
-- dbt (Jinja)WHERE status IN {{ var('active_statuses') }}
-- Dataform (JavaScript)WHERE status IN ${dataform.projectConfig.vars.active_statuses}Logique conditionnelle :
-- dbt (Jinja){% if target.name == 'prod' %} AND created_at > CURRENT_DATE - 90{% endif %}
-- Dataform (JavaScript)${when(dataform.projectConfig.defaultDatabase === 'prod', "AND created_at > CURRENT_DATE - 90")}Boucles :
-- dbt (Jinja){% for status in ['active', 'pending', 'churned'] %} {% if not loop.first %} UNION ALL {% endif %} SELECT '{{ status }}' AS customer__status, COUNT(*) AS customer__total FROM {{ ref('base__stripe__customers') }} WHERE customer__status = '{{ status }}'{% endfor %}
-- Dataform (JavaScript)${["active", "pending", "churned"] .map(status => `SELECT '${status}' AS customer__status, COUNT(*) AS customer__total FROM ${ref("base__stripe__customers")} WHERE customer__status = '${status}'`) .join(" UNION ALL ")}La version Jinja est plus verbeuse mais sans doute plus lisible pour quelqu’un qui n’écrit pas du JavaScript au quotidien. La version JavaScript est plus concise mais demande une aisance avec les méthodes de tableau et les template literals.
Où Jinja excelle
Courbe d’apprentissage pour les praticiens SQL. La plupart des analytics engineers viennent du SQL et ont appris Python en chemin. La syntaxe Jinja ressemble à du « SQL avec des variables » plutôt qu’à du « JavaScript qui génère du SQL ». Le modèle mental est plus simple : écrire du SQL, saupoudrer quelques {{ }} là où on a besoin de valeurs dynamiques.
Séparation des responsabilités. dbt conserve la configuration dans des fichiers YAML séparés de la logique SQL. Les tests, la documentation et les descriptions de colonnes vivent dans des fichiers schema. Le fichier SQL se concentre sur la transformation elle-même. Certaines équipes préfèrent cette séparation explicite.
Écosystème mature de macros. Le hub de packages dbt compte des centaines de macros pré-construites. dbt-utils fournit surrogate_key, pivot, unpivot et des dizaines d’autres. dbt-expectations porte les 50+ tests de Great Expectations. dbt-date gère les calendriers fiscaux et les date spines. Vous avez rarement besoin d’écrire des utilitaires from scratch.
Les macros comme snippets SQL. Une macro Jinja ressemble à une fonction SQL :
{% macro cents_to_dollars(column_name) %} ROUND({{ column_name }} / 100.0, 2){% endmacro %}
-- UtilisationSELECT order_id, {{ cents_to_dollars('amount_cents') }} AS order__amount_dollarsFROM {{ ref('base__stripe__orders') }}Ce pattern permet aux macros de rester des helpers SQL plutôt que de la génération de code arbitraire.
Où JavaScript excelle
Génération dynamique de modèles. C’est là que JavaScript prend le plus clairement l’avantage. Besoin de créer la même structure de modèle pour 50 pays ? Avec dbt, vous utiliseriez dbt_codegen ou des scripts externes. Avec Dataform, vous écrivez un fichier JavaScript :
const countries = ["US", "GB", "FR", "DE", "JP", "AU"];
countries.forEach(country => { publish(`reporting_${country}`) .dependencies(["base__ga4__events"]) .query(ctx => ` SELECT event_id, event_name, event_timestamp, user_pseudo_id FROM ${ctx.ref("base__ga4__events")} WHERE country_code = '${country}' `);});Cela génère six modèles pleinement fonctionnels avec un suivi correct des dépendances.
Capacités complètes du langage. JavaScript vous donne les modules, les imports, les classes, async/await et les packages npm. Besoin de parser un fichier de configuration JSON, appeler une API pendant la compilation ou implémenter une logique métier complexe ? JavaScript gère ça naturellement. Jinja nécessite des contournements ou un prétraitement externe.
Configuration inline. Dataform garde la configuration avec le SQL :
config { type: "incremental", uniqueKey: ["order_id"], bigquery: { partitionBy: "DATE(created_at)", clusterBy: ["customer_id"] }, assertions: { uniqueKey: ["order_id"], nonNull: ["order_id", "customer_id"] }}
SELECT ...Tout ce qui concerne le modèle (matérialisation, partitionnement, tests) vit dans un seul fichier. Certaines équipes trouvent cela plus facile à maintenir que de naviguer entre fichiers SQL et YAML.
Familiarité pour les développeurs JavaScript. Les ingénieurs qui passent du développement web à la data trouvent l’approche de Dataform naturelle. Les template literals, les arrow functions et les méthodes de tableau sont des outils du quotidien. La courbe d’apprentissage se résume à « le SQL plus ce que je connais déjà ».
Patterns concrets comparés
Modèles incrémentaux
Les deux outils supportent le traitement incrémental, mais la syntaxe diffère :
dbt :
{{ config( materialized='incremental', unique_key='event_id', incremental_strategy='merge' )}}
SELECT event_id, event_name, event_timestamp, user_pseudo_idFROM {{ ref('base__ga4__events') }}{% if is_incremental() %} WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM {{ this }}){% endif %}Dataform :
config { type: "incremental", uniqueKey: ["event_id"]}
SELECT event_id, event_name, event_timestamp, user_pseudo_idFROM ${ref("base__ga4__events")}${when(incremental(), `WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM ${self()})`)}Les deux versions sont quasi identiques, ne différant que par la préférence syntaxique.
Logique spécifique à l’environnement
dbt :
{% if target.name == 'dev' %} {{ limit_data_in_dev(ref('base__ga4__events'), 1000) }}{% else %} SELECT event_id, event_name, event_timestamp, user_pseudo_id FROM {{ ref('base__ga4__events') }}{% endif %}Dataform :
${when( dataform.projectConfig.defaultDatabase === 'dev_project', `SELECT event_id, event_name, event_timestamp, user_pseudo_id FROM ${ref("base__ga4__events")} LIMIT 1000`, `SELECT event_id, event_name, event_timestamp, user_pseudo_id FROM ${ref("base__ga4__events")}`)}Là encore, des résultats équivalents. L’objet target de dbt est conçu spécifiquement pour ce cas d’usage, tandis que Dataform utilise la configuration du projet.
Helpers réutilisables
Macro dbt (macros/unnest_event_param.sql) :
{% macro unnest_event_param(param_name, value_type='string_value') %}(SELECT value.{{ value_type }} FROM UNNEST(event_params) WHERE key = '{{ param_name }}'){% endmacro %}Include Dataform (includes/helpers.js) :
function unnestEventParam(paramName, valueType = 'string_value') { return `(SELECT value.${valueType} FROM UNNEST(event_params) WHERE key = '${paramName}')`;}
module.exports = { unnestEventParam };Les deux approches fonctionnent. La version JavaScript peut être importée dans n’importe quel fichier ; la macro Jinja est disponible globalement. Aucune n’a d’avantage significatif pour des utilitaires simples.
La divergence sur les tests
Les tests sont le domaine où les écosystèmes diffèrent réellement.
Options de test dbt :
- Tests de schéma en YAML (unique, not_null, accepted_values, relationships)
- Tests génériques personnalisés
- Tests unitaires pour la logique de transformation (introduits dans dbt 1.8)
- Package
dbt-expectationsavec 50+ tests statistiques et de patterns - Elementary pour la détection d’anomalies et l’observabilité
Options de test Dataform :
- Assertions inline pour l’unicité, les vérifications de null et les conditions sur les lignes
- Fichiers d’assertion personnalisés avec des requêtes SQL
- Package communautaire
dataform-assertions(portée limitée)
-- Assertions Dataformconfig { type: "table", assertions: { uniqueKey: ["customer_id"], nonNull: ["customer_id", "email"], rowConditions: ['email LIKE "%@%.%"'] }}Les assertions intégrées de Dataform gèrent bien les cas courants. Mais les équipes ayant besoin de tests de distribution statistique, de comparaisons inter-tables ou de détection d’anomalies trouveront l’écosystème dbt plus complet. Si vous voulez des tests dans le style Great Expectations, dbt les propose ; Dataform non.
Choisir selon votre contexte
Il n’y a pas de réponse universellement correcte. Quelques facteurs tendent à orienter la décision.
Les compétences existantes de votre équipe comptent le plus. Si tout le monde connaît Python et peine avec JavaScript, Jinja sera naturel. Si vous recrutez des profils issus du développement web, JavaScript pourrait accélérer l’onboarding.
La complexité du projet influence le choix. Des pipelines de transformation simples fonctionnent bien avec les deux outils. Les projets nécessitant beaucoup de métaprogrammation (génération dynamique de modèles, logique conditionnelle complexe, intégration avec des systèmes externes) favorisent les capacités complètes de JavaScript en tant que langage.
Les exigences de test peuvent faire pencher la balance. Les équipes avec des besoins stricts en qualité des données bénéficient de l’écosystème de test mature de dbt. Si votre équipe a besoin de détection d’anomalies statistiques plutôt que d’assertions basiques, la bibliothèque de packages dbt vous offre bien plus de possibilités.
L’engagement plateforme joue un rôle. Dataform ne fonctionne qu’avec BigQuery. Si le support multi-warehouse compte aujourd’hui ou pourrait compter demain, dbt est la seule option. Si vous êtes engagés sur BigQuery de manière permanente, ce n’est pas un facteur.
La portabilité de carrière mérite d’être pesée. dbt domine les offres d’emploi en analytics engineering. L’expertise Dataform est précieuse mais plus niche. Pour les praticiens individuels, les compétences dbt sont transférables à davantage d’opportunités.
Le langage de templating en lui-même détermine rarement le succès d’un projet. Une logique de transformation claire, une bonne couverture de tests et un code maintenable comptent bien plus que d’écrire {{ ref() }} ou ${ref()}. Choisissez l’outil qui correspond à votre équipe et à vos contraintes, puis concentrez-vous sur ce que vous construisez.