Aller au contenu principal

Langage d'expressions (FQL)

Partout où Fluhoms demande une expression — filtrer des lignes, router un flux, poser une assertion, calculer une colonne — c'est ce langage qui est écrit. Il ressemble volontairement au SQL : IS NULL, LIKE, CASE WHEN, COALESCE.

Pourquoi un langage, et pas « du pandas »

Les expressions partaient autrefois telles quelles dans pandas. Vos pipelines en dépendaient donc directement : changer de moteur de calcul les aurait tous cassés. FQL est un contrat — la même expression continuera de valoir la même chose, quel que soit le moteur qui l'exécute demain.

En un coup d'œil​

-- Filtrer
STATUS IS NOT NULL AND AMOUNT > THRESHOLD

-- Router
STATUS IN ('OPEN', 'PENDING') OR LABEL LIKE 'urgent%'

-- Calculer une colonne
CASE WHEN AMOUNT IS NULL THEN 'À VÉRIFIER'
WHEN AMOUNT > 1000 THEN 'GROS'
ELSE 'STANDARD' END

Colonnes et valeurs​

Vous écrivezCe que ça désigne
AMOUNTla colonne AMOUNT
`Montant TTC`une colonne dont le nom contient des espaces
'texte'un texte (guillemets simples ou doubles)
42, 3.14un nombre
TRUE, FALSEun booléen
NULLl'absence de valeur
NULL n'est pas 'NULL'

LABEL = 'NULL' compare au texte « NULL ». Pour tester l'absence de valeur, écrivez LABEL IS NULL.

Comparer​

OpérateurExemple
= ou ==STATUS = 'OPEN'
!= ou <>STATUS <> 'OPEN'
< <= > >=AMOUNT >= THRESHOLD
IS NULL / IS NOT NULLCLOSED_AT IS NULL
IN (…) / NOT IN (…)STATUS IN ('OPEN', 'PENDING')
BETWEEN … AND …AMOUNT BETWEEN 10 AND 100
LIKE / NOT LIKELABEL LIKE 'fact%' (% = n'importe quoi, _ = un caractère)

Les comparaisons ignorent la casse pour LIKE, et comparent en texte dès que l'un des deux côtés est un texte : un identifiant lu 42 dans un CSV répond bien à ID = '42'.

Une valeur vide ne satisfait aucune comparaison

STATUS <> 'OPEN' ne ramène pas les lignes sans statut : on ne sait rien d'elles, elles ne sont pas « autres ». Pour les viser, écrivez-le : STATUS IS NULL OR STATUS <> 'OPEN'.

NOT, lui, nie le résultat : STATUS NOT IN ('OPEN') garde les lignes vides, puisqu'elles ne satisfaisaient pas le IN.

Combiner​

AND, OR, NOT, et des parenthèses. AND lie plus fort que OR :

AMOUNT > 1000 OR STATUS = 'OPEN' AND AMOUNT > 10
-- se lit : AMOUNT > 1000 OU (STATUS = 'OPEN' ET AMOUNT > 10)

Fonctions​

Texte​

FonctionEffet
UPPER(x) / LOWER(x)met en majuscules / minuscules
TRIM(x)retire les espaces de début et de fin
LENGTH(x)nombre de caractères
SUBSTR(x, début, longueur)extrait (le premier caractère est en position 1)
REPLACE(x, avant, après)remplace un texte
CONCAT(a, b, …) ou a || bmet bout à bout
LTRIM(x, car?) / RTRIM(x, car?)retire les espaces — ou les caractères donnés — d'un seul côté
LPAD(x, n, car?) / RPAD(x, n, car?)complète jusqu'à n caractères (espace par défaut)
RIGHT(x, n)les n derniers caractères
POSITION(x, cherché)position du texte cherché, à partir de 1 ; 0 s'il est absent
OCCURRENCES(x, cherché)nombre d'apparitions
REGEXP_REPLACE(x, motif, remplacement)remplace par expression régulière ; \1 reprend un groupe capturé

Valeurs absentes​

FonctionEffet
COALESCE(a, b, …)la première valeur renseignée
IFNULL(a, b)b quand a est vide

Nombres​

ABS(x), ROUND(x, décimales), FLOOR(x), CEIL(x), et les opérations + - * / %.

Le flux​

FonctionEffet
NB_LIGNES()le nombre de lignes du lot, identique sur chaque ligne

C'est le seul endroit où une ligne apprend quelque chose de ses sœurs. Elle se combine à une condition ordinaire :

MONTANT > 50 AND NB_LIGNES() < 100
Elle ne permet pas de router un flux VIDE

Une expression s'évalue sur les lignes. Sans aucune ligne, il n'y a aucun support pour porter la réponse : NB_LIGNES() = 0 rend un résultat vide, et un Aiguillage — qui répartit des lignes — n'émet alors rien du tout.

Pour décider sur un lot, y compris vide, c'est Aiguillage de flux : il choisit une sortie sans avoir besoin d'une ligne pour le faire.

Dates​

FonctionEffet
TODAY()la date du jour, à minuit — comparable à une date sans que l'heure de l'exécution s'en mêle
NOW()l'instant de l'exécution, heure comprise
YEAR(d) / MONTH(d) / DAY(d)l'année, le mois, le jour d'une date
HOUR(d) / MINUTE(d) / SECOND(d)l'heure, les minutes, les secondes — des nombres, donc comparables
FORMAT_DATE(d, format)écrit la date en texte — %d jour, %m mois, %Y année, %H:%M:%S l'heure, %3f les millisecondes
DATE_TRUNC(d, unité?)ramène la date au début de l'unité : milliseconds, seconds (défaut), minutes, hours, days
DATE_DIFF(fin, début, unité?)écart entre deux dates, tronqué : days (défaut), hours, minutes, seconds
Ajouter l'heure : c'est NOW(), pas TODAY()

TODAY() est une date, ramenée à minuit. Lui demander une heure répond donc toujours la même chose, et sans erreur — c'est ce qui rend le piège coûteux :

FORMAT_DATE(TODAY(), '%d/%m/%Y %H:%M:%S')  -- …  00:00:00, toujours
FORMAT_DATE(NOW(), '%d/%m/%Y %H:%M:%S') -- l'heure de l'exécution

Pour une colonne « date et heure d'import », c'est NOW(). TODAY() reste le bon choix pour comparer à une date, où l'heure de l'exécution n'a rien à décider.

Les secondes s'écrivent %S, en majuscule

%s en minuscule n'est pas les secondes : c'est l'heure Unix. '%H:%M:%s' rendait 14:05:1772888709 — dix chiffres au milieu de l'horodatage, sans erreur. Le langage le refuse désormais, et dit quoi écrire à la place.

Les fractions de seconde

%f seul écrit six chiffres — c'est la règle de strftime, et il n'y a aucun moyen d'en obtenir trois. %1f à %6f choisissent donc le nombre de décimales : %3f donne les millisecondes. Un seul de ces jetons par format.

Écrire une date en base : aucun formatage, mais une précision à connaître

Une expression qui rend une DATE est écrite comme telle — le pipeline ne fabrique pas de texte, et la base applique la précision de sa colonne. Il n'y a donc jamais besoin de FORMAT_DATE pour écrire dans une colonne de date.

En revanche la colonne, elle, modifie la valeur. Mesuré sur SQL Server 2022 :

Valeur calculéeColonne datetime2(3)Colonne datetime
…09.123999…09.124 (arrondi)…09.123
…09.124…09.124…09.123
…09.456…09.456…09.457
…09.999…09.999…10.000 — seconde suivante

datetime2(3) arrondit à la milliseconde : DATE_TRUNC(d, 'milliseconds') avant l'écriture rend l'écrit identique au calculé.

datetime est un autre animal : il ne stocke pas des millisecondes mais des 1/300ᵉ de seconde, et y arrondit. Tronquer à la milliseconde n'y suffit donc pas — .124 se décale quand même. Seule une valeur tombant sur la grille fait un aller-retour exact, et la seconde pleine en fait partie : DATE_TRUNC(d, 'seconds').

Quand l'écart ne porte à conséquence ni pour une comparaison, ni pour une déduplication, ni pour un rapprochement, il est inférieur à 1,7 ms et ne mérite pas qu'on s'en occupe.

Un écart est tronqué, jamais arrondi

DATE_DIFF entre le 7 mars à minuit et le 8 mars à 23 h rend 1, pas 2. Arrondir ferait basculer un délai au-dessus d'un seuil sans que rien ne le dise.

-- Une colonne « date d'import »
TODAY()

-- Les factures de plus de 30 jours
DATE_FACTURE < TODAY() - 30

Convertir​

CAST(AMOUNT AS STRING)
CAST(LABEL AS INTEGER)

Types acceptés : STRING, INTEGER, FLOAT, BOOLEAN, DATE.

Choisir selon les lignes : CASE WHEN​

CASE WHEN STATUS IS NULL     THEN 'INCONNU'
WHEN AMOUNT > THRESHOLD THEN 'AU-DESSUS'
ELSE 'EN DESSOUS' END

Le premier WHEN satisfait gagne, de haut en bas. Sans ELSE, les lignes restantes sont vides.

Sans écrire une ligne

La brick Colonnes → Valeurs → Colonne conditionnelle construit exactement la même règle à la souris, conditions imbriquées comprises.

Ce que le langage ne fait pas​

Ni jointure, ni agrégat, ni sous-requête : ce sont des bricks, avec leur propre panneau — Jointure, Agrégations, SQL. Une expression reste une règle qui se lit sur une ligne.

NB_LIGNES() est la seule exception, et une exception limitée : elle rapporte un fait du lot à chaque ligne, elle n'agrège rien. Et elle ne dit plus rien quand il n'y a plus de ligne à qui le rapporter.

Sécurité​

Une expression n'est jamais exécutée comme du code : elle est analysée, puis évaluée par le moteur. Elle ne peut appeler que les fonctions de cette page — ni lire un fichier, ni atteindre le système.

Messages d'erreur​

MessageCe qu'il faut corriger
la colonne « X » n'existe pasle nom ne correspond à aucune colonne à cette étape du flux
« X » n'est pas une fonction du langagefonction inconnue — la liste des fonctions disponibles suit le message
« ) » attenduparenthèse, guillemet ou mot-clé manquant, à la position indiquée