Skip to content

SAS → Noria

Philosophie

Le code SAS se réécrit principalement en SQL pur dans Noria. Les DATA step deviennent des SELECT, les PROC ont leurs équivalents SQL directs. Les macro-variables SAS (&param) deviennent des paramètres Jinja2 ({{ params.nom }}).

Correspondances

Gestion des bibliothèques

SAS Noria
LIBNAME dwh ... {{ params.schema }} dans le SQL
dwh.ma_table {{ params.schema }}.ma_table
WORK.temp Table temporaire sans target: (SELECT only)

DATA step

SAS Noria (SQL)
DATA out; SET source; SELECT * FROM source
WHERE condition; WHERE condition
KEEP col1 col2; SELECT col1, col2
DROP col; Lister les colonnes sans col
RENAME old=new; col AS new
IF ... THEN ...; CASE WHEN ... THEN ... END
IF ... THEN DELETE; WHERE NOT (condition)
new_col = expr; expr AS new_col dans SELECT
FORMAT col DDMMYY10.; STRFTIME(col, '%d/%m/%Y') (DuckDB)
RETAIN cumul 0; cumul + val; SUM(val) OVER (ORDER BY ...)
BY group; FIRST.group; ROW_NUMBER() OVER (PARTITION BY group ORDER BY ...)
LAG(col) LAG(col) OVER (ORDER BY ...)

PROC SQL

PROC SQL se réécrit directement — la syntaxe est très proche. Les principales différences :

SAS PROC SQL Noria
INTO :var (macro variable) {{ params.nom }} (résolu avant exécution)
CONNECT TO (pass-through) Connexion native via database:
%SYSFUNC dans le SQL Macro Jinja2

PROC utilitaires

SAS Noria
PROC SORT DATA=t BY col; SELECT * FROM t ORDER BY col
PROC MEANS DATA=t; SELECT AVG(col), MIN(col), MAX(col) FROM t GROUP BY ...
PROC FREQ DATA=t; TABLES col; SELECT col, COUNT(*) AS n FROM t GROUP BY col ORDER BY n DESC
PROC SUMMARY NWAY; SELECT ... FROM t GROUP BY ...
PROC TRANSPOSE DATA=t; UNPIVOT (DuckDB)
PROC APPEND BASE=b DATA=d; output_write: append
PROC DELETE DATA=t; output_write: replace (recrée la table)
PROC PRINT; noria compile --task nom (voir le SQL)

Macro SAS

SAS Noria
%LET param = valeur; params: {param: valeur} dans noria.yml
&param {{ params.param }}
%GLOBAL param; params: au niveau pipeline
%MACRO nom; ... %MEND; Macro Jinja2 dans sql/macros/nom.sql.j2
%nom(args) {{ nom(args) }} dans le SQL
%DO %TO %END; loop: (liste statique) ou loop_from_query:
%INCLUDE 'fichier.sas'; {% include 'fichier.sql' %} Jinja2
%IF &env = PROD %THEN ...; {% if params.env == 'prod' %} ... {% endif %}

Exemple complet

Code SAS (DATA step + PROC SQL)

%LET schema = DWH;
%LET annee = 2024;

/* Étape 1 : sélection et calcul */
PROC SQL;
  CREATE TABLE WORK.commandes_calc AS
  SELECT
    client_id,
    MONTH(date_commande)      AS mois,
    montant_ht,
    montant_ht * 1.20         AS montant_ttc
  FROM &schema..commandes
  WHERE YEAR(date_commande) = &annee
    AND statut = 'V';
QUIT;

/* Étape 2 : agrégation */
PROC SQL;
  CREATE TABLE &schema..stats_client AS
  SELECT
    client_id,
    mois,
    SUM(montant_ttc) AS ca_ttc,
    COUNT(*)         AS nb_commandes
  FROM WORK.commandes_calc
  GROUP BY client_id, mois;
QUIT;

Réécriture Noria

params:
  schema: DWH
  annee: 2024

nodes:
  commandes_calc:
    sql: sql/commandes_calc.sql
    target: "{{ params.schema }}.commandes_calc"
    output_write: replace
    description: "Commandes validées avec montant TTC — {{ params.annee }}"

  stats_client:
    sql: sql/stats_client.sql
    target: "{{ params.schema }}.stats_client"
    output_write: replace
    description: "Statistiques mensuelles par client"
    needs: [commandes_calc]
-- sql/commandes_calc.sql
SELECT
    client_id,
    EXTRACT(MONTH FROM date_commande)       AS mois,
    ROUND(montant_ht, 2)                   AS montant_ht,
    ROUND(montant_ht * 1.20, 2)           AS montant_ttc
FROM {{ params.schema }}.commandes
WHERE EXTRACT(YEAR FROM date_commande) = {{ params.annee }}
  AND statut = 'V'
-- sql/stats_client.sql
SELECT
    client_id,
    mois,
    ROUND(SUM(montant_ttc), 2)  AS ca_ttc,
    COUNT(*)                    AS nb_commandes
FROM {{ params.schema }}.commandes_calc
GROUP BY client_id, mois
ORDER BY client_id, mois

Note : Noria exécute le pipeline en une seule connexion. La table intermédiaire commandes_calc peut rester en base (elle sera recréée à chaque run grâce à output_write: replace). Si tu veux éviter de la persister, tu peux fusionner les deux étapes en un seul SQL avec une CTE.