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.
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 écrivez | Ce que ça désigne |
|---|---|
AMOUNT | la colonne AMOUNT |
`Montant TTC` | une colonne dont le nom contient des espaces |
'texte' | un texte (guillemets simples ou doubles) |
42, 3.14 | un nombre |
TRUE, FALSE | un booléen |
NULL | l'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érateur | Exemple |
|---|---|
= ou == | STATUS = 'OPEN' |
!= ou <> | STATUS <> 'OPEN' |
< <= > >= | AMOUNT >= THRESHOLD |
IS NULL / IS NOT NULL | CLOSED_AT IS NULL |
IN (…) / NOT IN (…) | STATUS IN ('OPEN', 'PENDING') |
BETWEEN … AND … | AMOUNT BETWEEN 10 AND 100 |
LIKE / NOT LIKE | LABEL 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'.
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
| Fonction | Effet |
|---|---|
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 || b | met 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
| Fonction | Effet |
|---|---|
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
| Fonction | Effet |
|---|---|
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
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
| Fonction | Effet |
|---|---|
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 |
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.
%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.
%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.
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ée | Colonne 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.
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.
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
| Message | Ce qu'il faut corriger |
|---|---|
la colonne « X » n'existe pas | le nom ne correspond à aucune colonne à cette étape du flux |
« X » n'est pas une fonction du langage | fonction inconnue — la liste des fonctions disponibles suit le message |
« ) » attendu | parenthèse, guillemet ou mot-clé manquant, à la position indiquée |