Skip to content

Macros SQL cross-DB

Les macros Noria sont des fonctions Jinja2 qui génèrent du SQL adapté au dialecte cible. Écrire {{ cast('col', 'VARCHAR') }} produit CAST(col AS VARCHAR) en DuckDB et col::VARCHAR en PostgreSQL — sans changer le code SQL.

Macros built-in

cast(col, type) — conversion de type

{{ cast('montant', 'DECIMAL(18,2)') }}
{{ cast('id', 'VARCHAR') }}
{{ cast('date_str', 'DATE') }}
Dialecte Résultat
DuckDB CAST(montant AS DECIMAL(18,2))
PostgreSQL montant::DECIMAL(18,2)
BigQuery CAST(montant AS DECIMAL)

date_trunc(period, col) — tronquer une date

{{ date_trunc('month', 'date_commande') }}   -- premier jour du mois
{{ date_trunc('year',  'date_commande') }}   -- premier jour de l'année
{{ date_trunc('week',  'date_commande') }}   -- premier jour de la semaine
{{ date_trunc('day',   'ts') }}              -- minuit
{{ date_trunc('hour',  'ts') }}

Périodes disponibles : second, minute, hour, day, week, month, quarter, year.

Dialecte Résultat
DuckDB DATE_TRUNC('month', date_commande)
PostgreSQL DATE_TRUNC('month', date_commande)
BigQuery DATE_TRUNC(date_commande, MONTH)

current_timestamp() — timestamp courant

{{ current_timestamp() }}    AS updated_at
Dialecte Résultat
DuckDB CURRENT_TIMESTAMP
PostgreSQL NOW()
BigQuery CURRENT_TIMESTAMP()

concat(*args) — concaténation

{{ concat('prenom', "' '", 'nom') }}
{{ concat('code_pays', "'-'", 'code_region') }}
Dialecte Résultat
DuckDB CONCAT(prenom, ' ', nom)
PostgreSQL CONCAT(prenom, ' ', nom)
BigQuery CONCAT(prenom, ' ', nom)

coalesce(*args) — première valeur non NULL

{{ coalesce('email_pro', 'email_perso', "'inconnu'") }}

Tous les dialectes : COALESCE(email_pro, email_perso, 'inconnu')


if_null(col, default) — valeur par défaut si NULL

{{ if_null('commentaire', "'aucun'") }}
{{ if_null('montant', '0') }}

Tous les dialectes : COALESCE(commentaire, 'aucun')


safe_divide(numerator, denominator) — division sans risque de division par zéro

{{ safe_divide('ca_total', 'nb_commandes') }}             AS panier_moyen
ROUND({{ safe_divide('nb_livres', 'nb_total') }} * 100, 1) AS taux_livraison

Tous les dialectes : CASE WHEN nb_commandes = 0 THEN NULL ELSE ca_total / nb_commandes END


to_char(col, format) — formater une date en texte

{{ to_char(date_trunc('month', 'date_op'), 'YYYY-MM') }}   AS mois_label
{{ to_char('date_naissance', 'DD/MM/YYYY') }}              AS date_fr
Dialecte Résultat
DuckDB STRFTIME(col, 'YYYY-MM')
PostgreSQL TO_CHAR(col, 'YYYY-MM')
BigQuery FORMAT_TIMESTAMP('YYYY-MM', col)

limit(n) — limiter le nombre de lignes

SELECT * FROM ma_table
{{ limit(100) }}

Tous les dialectes : LIMIT 100


schema_table(schema, table) — référence qualifiée

SELECT * FROM {{ schema_table(params.schema, 'clients') }}

Résultat : main.clients (ou juste clients si schema est None)


Combiner les macros

Les macros se combinent naturellement :

SELECT
    id_client,
    {{ cast('id_client', 'VARCHAR') }}                       AS id_str,
    {{ to_char(date_trunc('month', 'date_inscription'), 'YYYY-MM') }} AS mois_inscription,
    {{ coalesce('email_pro', 'email_perso', "'inconnu'") }}  AS email,
    ROUND({{ safe_divide('ca_total', 'nb_commandes') }}, 2) AS panier_moyen,
    {{ current_timestamp() }}                                AS updated_at
FROM {{ params.schema }}.clients
WHERE date_inscription >= {{ date_trunc('year', 'CURRENT_DATE') }}

Macros utilisateur

Crée des macros métier réutilisables dans sql/macros/ (fichiers .sql ou .sql.j2). Elles sont auto-chargées — aucun import nécessaire.

-- sql/macros/metier.sql.j2

{# Catégorise un montant de commande #}
{% macro categorie_panier(col) %}
CASE
  WHEN {{ col }} < 50  THEN 'Petit'
  WHEN {{ col }} < 200 THEN 'Moyen'
  ELSE                      'Gros'
END
{% endmacro %}

{# Taux en pourcentage avec arrondi #}
{% macro taux_pct(num, denom, decimals=1) %}
ROUND({{ safe_divide(num, denom) }} * 100, {{ decimals }})
{% endmacro %}

Utilisation dans n'importe quel SQL du projet :

SELECT
    id_commande,
    montant,
    {{ categorie_panier('montant') }}                      AS categorie,
    {{ taux_pct('nb_livres', 'nb_commandes') }}           AS taux_livraison
FROM {{ params.schema }}.commandes

Surcharger une macro built-in

Si tu définis une macro utilisateur avec le même nom qu'une macro built-in, ta version a la priorité :

-- sql/macros/override.sql
{% macro current_timestamp() %}GETDATE(){% endmacro %}
-- Maintenant {{ current_timestamp() }} → GETDATE() partout dans ce projet

Appeler des macros built-in depuis une macro utilisateur

{% macro mois_label(date_col) %}
{{ to_char(date_trunc('month', date_col), 'YYYY-MM') }}
{% endmacro %}