Comment valider les données dans SQL Server
Un guide pratique pour valider la qualité des données dans Microsoft SQL Server — les six contrôles dont chaque table a besoin, le T-SQL pour les écrire, et comment en faire un contrat de données versionné qui s'exécute selon une planification.
· 9 min read
Le moyen le plus rapide de valider des données dans SQL Server consiste à exprimer chaque attente sous la forme d'une requête d'agrégation unique qui renvoie un nombre de violations, à les exécuter toutes sur la table en une seule passe, et à faire échouer le lot dès qu'un compteur franchit son seuil. Six contrôles couvrent l'essentiel des défaillances réelles : les valeurs nulles dans les colonnes obligatoires, les clés en doublon, les valeurs hors d'un ensemble autorisé, les nombres hors d'une plage plausible, les chaînes mal formées et les lignes périmées. Ce guide présente le T-SQL de chacun, les particularités de SQL Server qui vous piégeront, et la façon de passer de scripts ponctuels à un contrat qui s'exécute selon une planification.
Pourquoi SQL Server mérite une approche à part
La plupart des conseils sur la qualité des données sont écrits pour Postgres et supposent, sans le dire, des fonctionnalités que T-SQL n'a pas. Trois différences comptent :
- Il n'existe pas d'opérateur d'expression régulière.
LIKEest la seule correspondance de motif fournie. Il accepte les classes de caractères[0-9]et les jokers%/_, ce qui suffit pour des motifs ancrés à forme fixe, et pas davantage. - La limitation de lignes s'écrit
TOP n, pasLIMIT n, et se place avant la liste des colonnes plutôt qu'à la fin de l'instruction. Tout outil qui construit des requêtes d'échantillonnage doit le savoir. - Le classement (collation) décide de l'égalité. Une colonne en
SQL_Latin1_General_CP1_CI_ASpar défaut considère'ACTIVE'et'active'comme une même valeur. Un classement sensible à la casse, non. Votre contrôle de « valeurs autorisées » signifie donc discrètement deux choses différentes sur deux serveurs.
Tout ce qui suit tient compte de ces points. Pour les aspects identiques sur tous les moteurs — où exécuter les contrôles, comment les planifier, quelles tables traiter en priorité — commencez par le guide complet de la validation des données.
Les six contrôles dont chaque table a besoin
1. Complétude — valeurs nulles dans les colonnes obligatoires
SELECT COUNT_BIG(*) AS violations
FROM [sales].[orders]
WHERE [order_id] IS NULL;
Utilisez COUNT_BIG plutôt que COUNT sur les grandes tables de faits : COUNT renvoie un int et déborde au-delà de 2,1 milliards de lignes. Notez aussi que COUNT(colonne) ignore déjà les valeurs nulles, d'où l'écriture du contrôle sous la forme d'un COUNT(*) filtré — les deux sont faciles à confondre, et la mauvaise version renvoie toujours zéro violation.
2. Unicité — clés métier en doublon
SELECT COUNT_BIG(*) AS violations
FROM (
SELECT [order_id]
FROM [sales].[orders]
WHERE [order_id] IS NOT NULL
GROUP BY [order_id]
HAVING COUNT(*) > 1
) AS dupes;
Une contrainte de clé primaire empêcherait le problème, mais les tables analytiques sont généralement chargées par un traitement ELT dans un tas (heap) dépourvu de toute contrainte. Le contrôle de doublons est le prix d'une contrainte manquante.
3. Conformité — valeurs hors de l'ensemble autorisé
SELECT COUNT_BIG(*) AS violations
FROM [sales].[orders]
WHERE [status] IS NOT NULL
AND [status] NOT IN ('pending', 'paid', 'shipped', 'refunded');
Gardez la condition IS NOT NULL. Avec une valeur nulle à gauche, NOT IN s'évalue à UNKNOWN, la ligne est écartée, et une colonne nulle à 40 % paraît parfaitement conforme.
4. Exactitude — nombres hors d'une plage plausible
SELECT COUNT_BIG(*) AS violations
FROM [sales].[orders]
WHERE [total_amount] IS NOT NULL
AND ([total_amount] < 0 OR [total_amount] > 100000);
Les contrôles de plage attrapent les erreurs d'unité — des centimes chargés comme des euros, une décimale décalée par une conversion de devise — qu'aucune contrainte de schéma ne verra jamais.
5. Conformité — identifiants mal formés
C'est ici que T-SQL diverge. Il n'y a pas d'opérateur ~ : un motif ancré doit donc s'exprimer avec LIKE.
-- ^ORD-[0-9]{6}$ becomes:
SELECT COUNT_BIG(*) AS violations
FROM [sales].[orders]
WHERE [reference] IS NOT NULL
AND [reference] NOT LIKE 'ORD-[0-9][0-9][0-9][0-9][0-9][0-9]';
LIKE est implicitement ancré aux deux extrémités lorsque le motif ne commence ni ne finit par % : c'est donc un véritable équivalent de l'expression régulière ancrée. Ce que vous ne pouvez pas exprimer, ce sont les alternatives, les groupes optionnels, les références arrière ou les répétitions non bornées. Pour ces cas, repliez-vous sur une liste explicite de valeurs autorisées ou sur un contrôle customSql écrit à la main, plutôt que de faire comme si LIKE était un moteur d'expressions régulières.
Catalyst effectue cette traduction pour vous : une règle regex sur une connexion SQL Server est convertie en motif LIKE équivalent lorsque c'est exprimable, et lève une erreur explicite « non pris en charge par ce dialecte » lorsque ce ne l'est pas — de sorte que le contrôle échoue bruyamment au lieu de passer en silence.
6. Actualité — fraîcheur
SELECT COUNT_BIG(*) AS violations
FROM [sales].[orders]
WHERE [created_at] < DATEADD(HOUR, -24, SYSUTCDATETIME());
Utilisez SYSUTCDATETIME(), pas GETDATE(). GETDATE() renvoie l'heure locale du serveur : le même contrôle dérive donc d'une heure deux fois par an, et de plusieurs heures entières d'une région à l'autre. Stockez les horodatages en datetime2 et comparez en UTC.
Créer une connexion en lecture seule sans risque
La validation ne devrait jamais avoir besoin d'un accès en écriture. Une connexion minimale ressemble à ceci :
CREATE LOGIN catalyst_ro WITH PASSWORD = '<generated>';
CREATE USER catalyst_ro FOR LOGIN catalyst_ro;
ALTER ROLE db_datareader ADD MEMBER catalyst_ro;
GRANT VIEW DEFINITION TO catalyst_ro; -- needed to read INFORMATION_SCHEMA
db_datareader accorde le SELECT sur toutes les tables ; VIEW DEFINITION est ce qui permet au compte d'énumérer les colonnes et les types depuis INFORMATION_SCHEMA.COLUMNS. Pointez-la vers un réplica secondaire lisible si vous disposez d'un groupe de disponibilité — les requêtes de validation sont des balayages d'agrégation et n'ont rien à faire sur l'instance primaire.
Du SQL ponctuel au contrat de données
Des scripts comme ceux ci-dessus se dégradent. Ils vivent dans le dépôt de quelqu'un, plus personne ne sait lesquels tournent encore, et les seuils restent invisibles pour les analystes qui en dépendent. La solution consiste à exprimer les attentes de manière déclarative, dans un format lisible à la fois par des humains et par des outils.
Catalyst s'appuie pour cela sur l'Open Data Contract Standard (ODCS). Les six contrôles ci-dessus deviennent :
apiVersion: v3.0.0
kind: DataContract
info:
title: orders
version: 1.2.0
owner: data-platform
schema:
- name: orders
physicalName: orders
physicalType: table
properties:
- name: order_id
logicalType: string
physicalType: uniqueidentifier
required: true
primaryKey: true
quality:
- rule: nullCount
dimension: completeness
severity: error
mustBe: "0"
- rule: duplicateCount
dimension: uniqueness
severity: error
mustBe: "0"
- name: status
logicalType: string
quality:
- rule: validValues
dimension: conformity
severity: error
mustBe: "['pending', 'paid', 'shipped', 'refunded']"
- name: total_amount
logicalType: number
quality:
- rule: between
dimension: accuracy
severity: error
mustBe: "[0, 100000]"
- name: reference
logicalType: string
quality:
- rule: regex
dimension: conformity
severity: warning
mustBe: "'^ORD-[0-9]{6}#39;"
- name: created_at
logicalType: timestamp
quality:
- rule: freshness
dimension: timeliness
severity: error
mustBe: "<= 24h"
Le YAML fait foi. Catalyst compile chaque entrée quality en T-SQL conforme au dialecte, tel que montré plus haut, exécute le lot sur votre serveur et consigne un résultat pass / warn / fail par contrôle. Modifier une règle dans l'éditeur visuel réécrit ce même YAML, commentaires et ordre préservés : le contrat relu dans une pull request est donc bien le contrat qui s'exécute.
Notez le champ severity. error fait échouer l'exécution ; warning consigne la violation et poursuit. Les contrôles de motif sur des champs saisis par des humains sont généralement des avertissements — ce que vous voulez, c'est la tendance, pas une astreinte à 3 h du matin.
Planification et alertes
Une validation que vous lancez à la main est une validation que vous ne lancez qu'une fois. Attachez une planification au jeu de données — toutes les heures pour la fraîcheur d'une table en flux, quotidiennement après la fenêtre ELT pour le reste — et alertez sur la transition, pas sur l'état. Ce qui compte, c'est « orders vient de passer en échec », pas « orders est toujours en échec », qui est précisément ce qui apprend aux gens à ignorer le canal.
Deux remarques opérationnelles propres à SQL Server :
- Définissez un délai d'expiration de requête. Un
COUNT_BIG(*)sur l'ensemble d'un tas d'un milliard de lignes non indexé tournera volontiers pendant une heure. Catalyst applique un délai d'expiration par contrôle et signale le contrôle commeerror(distinct defail), pour qu'un problème d'infrastructure ne ressemble jamais à un problème de données. - Surveillez le cache de plans. Les balayages d'agrégation sans paramètres sont peu coûteux à compiler mais coûteux à exécuter. Sur de très grandes tables, validez une partition plutôt que la table entière, ou exécutez les contrôles sur une vue indexée filtrée.
Les pièges SQL Server à connaître
| Piège | Ce qui se passe | Que faire |
|---|---|---|
Débordement de COUNT(*) | Échec au-delà de 2,1 milliards de lignes | Utiliser COUNT_BIG(*) |
NOT IN avec des valeurs nulles | Lignes silencieusement exclues | Ajouter IS NOT NULL |
GETDATE() | Heure locale du serveur | Utiliser SYSUTCDATETIME() |
| Classement insensible à la casse | 'PAID' passe un contrôle sur 'paid' | Fixer le classement, ou normaliser avec COLLATE |
| Conversion implicite | WHERE varchar_col = 123 balaie toute la table | Comparer des types identiques |
Égalité sur des float | Bornes de plage faussées par l'arrondi | Utiliser decimal pour les montants |
Questions fréquentes
Puis-je valider des données dans SQL Server sans écrire de SQL ?
Oui. Définissez l'attente de manière déclarative — obligatoire, unique, valeurs autorisées, plage numérique, motif, âge maximal — et laissez l'outil la compiler en T-SQL. Catalyst importe les colonnes et les types depuis INFORMATION_SCHEMA, suggère un jeu de règles de référence à partir du schéma, et génère les requêtes. Vous n'écrivez du SQL que pour la logique véritablement sur mesure, via une règle customSql.
La validation de la qualité des données a-t-elle besoin d'un accès en écriture à ma base ?
Non. Chaque contrôle de ce guide est un SELECT qui renvoie un seul nombre. Une connexion db_datareader assortie de VIEW DEFINITION suffit. Catalyst se connecte en lecture seule et ne stocke que des métadonnées et des résultats de contrôles — les lignes elles-mêmes restent sur votre serveur.
Comment vérifier des expressions régulières en T-SQL ?
Directement, vous ne le pouvez pas : SQL Server n'a pas d'opérateur d'expression régulière. Les motifs ancrés de longueur fixe se traduisent en LIKE avec des classes de caractères de type [0-9]. Tout ce qui utilise des alternatives, des groupes optionnels ou des répétitions non bornées doit devenir une liste de valeurs autorisées, une fonction CLR ou un contrôle SQL personnalisé.
À quelle fréquence exécuter les validations ?
Alignez la planification sur la cadence propre des données, et lancez le contrôle juste après le chargement qui les produit. Les règles de fraîcheur sur des tables en flux appellent des exécutions horaires ; les tables de dimensions en batch n'en demandent qu'une, après la fin de l'ELT nocturne. Valider plus souvent que les données ne changent ne produit que du bruit.
Est-ce que cela fonctionne avec Azure SQL et Microsoft Fabric ?
Azure SQL Database expose la même surface T-SQL : tout ce qui précède s'applique sans changement. L'entrepôt de Microsoft Fabric et son point de terminaison SQL analytics parlent eux aussi T-SQL, avec la même absence d'expressions régulières — voyez le guide Microsoft Fabric pour les différences autour de l'authentification Entra ID et de la synchronisation des métadonnées OneLake.