IAMaîtriser l'IA générative Plan du corpus

01 — SQL et texte-vers-SQL

Un modèle qui ne connaît pas ton schéma invente tes colonnes. Lui donner le schéma, c'est 80 % du travail.

Temps de lecture : 15 min | Niveau : Intermédiaire

Ce que tu sauras faire après

  • Fournir ton schéma au modèle dans un format qu'il exploite vraiment.
  • Faire écrire une requête complexe sans te retrouver avec des colonnes inventées.
  • Faire optimiser une requête lente et savoir vérifier que l'optimisation est réelle.
  • Faire expliquer une requête héritée de quelqu'un d'autre.
  • Traduire une requête d'un dialecte SQL à un autre sans casser la logique.

1. Le vrai problème du texte-vers-SQL

Tu écris « donne-moi le chiffre d'affaires par mois ». Le modèle ne sait pas :

  • dans quelle table sont les ventes ;
  • si le montant est HT ou TTC ;
  • si la colonne s'appelle amount, montant, revenue ou ca_ttc ;
  • si les commandes annulées sont dans la même table ;
  • quel est ton dialecte SQL.

Il va donc deviner. Et une devinette SQL s'exécute parfois sans erreur. C'est le pire cas : tu as un chiffre, il est faux, rien ne clignote.

La documentation Anthropic sur le prompting le dit sans détour :

« Think of Claude as a brilliant but new employee who lacks context on your norms and workflows. The more precisely you explain what you want, the better the result. »

Et sur l'ancrage dans des documents fournis :

« Ground responses in quotes: For long document tasks, ask Claude to quote relevant parts of the documents first before carrying out its task. »

Appliqué au SQL, « les documents », c'est ton DDL.


2. Donner le schéma : la seule méthode qui marche

AVANT (mauvais)

Écris-moi une requête SQL qui donne le CA par mois et par magasin.

Résultat typique : SELECT DATE_TRUNC('month', order_date), store_name, SUM(total) FROM orders GROUP BY 1,2. Trois noms de colonnes sur quatre sont inventés.

APRÈS (bon)

Tu colles le CREATE TABLE réel. Pas une description en prose : le DDL.

-- Schéma fourni au modèle
CREATE TABLE analytics.fct_commandes (
  commande_id      STRING    NOT NULL,  -- clé primaire
  magasin_code     STRING,              -- 2 caractères, ex '03'
  date_commande    DATE,                -- date de prise de commande
  montant_ttc      NUMERIC,             -- en euros, TTC
  statut           STRING               -- 'validee' | 'annulee' | 'en_attente'
)
PARTITION BY date_commande
CLUSTER BY magasin_code;

CREATE TABLE analytics.dim_magasin (
  magasin_code     STRING NOT NULL,
  magasin_libelle  STRING,
  region           STRING
);

Trois choses changent tout :

  1. Les commentaires -- : ils portent le sens métier que le nom de colonne ne porte pas (montant_ttc en euros, TTC).
  2. Les valeurs possibles d'une colonne d'état : le modèle saura filtrer statut = 'validee' au lieu de tout additionner.
  3. Le partitionnement et le clustering : le modèle saura qu'il doit filtrer sur date_commande pour ne pas scanner toute la table.

Structurer avec des balises XML

La doc Anthropic recommande d'emballer chaque type de contenu dans sa propre balise :

« XML tags help Claude parse complex prompts unambiguously, especially when your prompt mixes instructions, context, examples, and variable inputs. »

Concrètement :

<schema>
{{COLLE TON DDL ICI}}
</schema>

<regles_metier>
- Le CA exclut toujours les commandes avec statut = 'annulee'.
- L'exercice fiscal va du 1er juillet au 30 juin.
</regles_metier>

<question>
CA mensuel par région sur l'exercice en cours.
</question>

Un détail qui compte quand le schéma est gros : la doc Anthropic recommande de placer les données longues en haut du prompt, au-dessus de la question. Schéma d'abord, question ensuite.


3. Écrire une requête complexe

Pour une requête à plusieurs niveaux (fenêtrage, CTE, anti-jointure), ajoute deux contraintes qui changent la qualité du résultat.

Contrainte 1 — imposer la structure en CTE. Une requête en CTE nommées est relisible ; une requête en sous-requêtes imbriquées ne l'est pas.

Contrainte 2 — demander les hypothèses avant le code. Tu forces le modèle à écrire ce qu'il a supposé. Tu lis les hypothèses, tu corriges celles qui sont fausses, puis seulement tu lis le SQL.

AVANT

Requête pour les clients qui ont commandé en janvier mais pas en février.

APRÈS

<schema>{{DDL}}</schema>

Écris une requête GoogleSQL (BigQuery) qui liste les clients ayant commandé
en janvier 2026 mais pas en février 2026.

Contraintes :
- Utilise des CTE nommées, une par étape logique. Pas de sous-requête imbriquée.
- Filtre sur la colonne de partitionnement dans CHAQUE CTE qui lit une table partitionnée.
- Exclus les commandes annulées.

Avant le SQL, liste sous <hypotheses> toutes les hypothèses que tu as dû faire
sur les données. Si le schéma ne te permet pas de répondre, dis-le au lieu
d'inventer un nom de colonne.

Pourquoi c'est mieux : le modèle a le schéma, il connaît la règle métier sur les annulations, il sait qu'il doit filtrer la partition, et il a le droit de dire qu'il lui manque une information. Cette dernière phrase est la technique de « l'issue de secours » que recommande Microsoft :

« Give the model an "out". It can sometimes be helpful to give the model an alternative path if it's unable to complete the assigned task. »


4. Optimiser une requête — et vérifier que c'est vrai

C'est là que la plupart des gens se font piéger. Le modèle te dira « cette version est plus performante ». Il ne l'a pas mesurée. Il raisonne sur des motifs, pas sur ton plan d'exécution.

Vérification 1 — le plan d'exécution

Sur PostgreSQL et MySQL : EXPLAIN donne le plan estimé, EXPLAIN ANALYZE exécute vraiment la requête et mesure.

Sur Snowflake, attention : EXPLAIN existe (USING JSON, USING TABULAR, USING TEXT) mais il ne rend que le plan logique. La doc est claire : « EXPLAIN compiles the SQL statement, but does not execute it, so EXPLAIN does not require a running warehouse. » Il n'y a donc pas d'EXPLAIN ANALYZE : pour les temps réellement passés, tu ouvres le Query Profile dans Snowsight.

Sur BigQuery, il n'y a pas de mot-clé EXPLAIN. Tu ouvres le bouton Execution Details dans la console, ou tu récupères le plan via la méthode d'API jobs.get, sous statistics.query.queryPlan.

Les champs du plan BigQuery à regarder en premier :

ChampCe qu'il te dit
recordsReadNombre de lignes lues en entrée de l'étape
recordsWrittenNombre de lignes produites en sortie
shuffleOutputBytesOctets échangés entre étapes
shuffleOutputBytesSpilledOctets débordés sur disque : signe d'une étape trop grosse
computeRatioAvg / waitRatioAvgOù part le temps : calcul, ou attente de slots

Un recordsRead énorme suivi d'un recordsWritten minuscule veut dire : tu lis beaucoup pour jeter beaucoup. C'est un filtre à remonter plus tôt.

Vérification 2 — la volumétrie et le coût

Sur BigQuery en facturation à la demande, tu paies les octets lus. Trois réflexes, tous documentés par Google :

  • Le dry run. L'option --dry_run de bq valide la requête et estime les octets traités, sans rien exécuter ni facturer. Retiens que c'est une estimation haute. La doc Google le dit mot pour mot : « The estimate of the number of bytes that is billed for a query is an upper bound, and can be higher than the actual number of bytes billed after running the query. » Tu peux donc payer moins que l'estimation, jamais plus.
  • Le plafond maximum bytes billed. Si l'estimation dépasse ta limite, « the query fails without incurring a charge ».
  • Le piège du LIMIT. La doc est formelle : « For non-clustered tables, don't use a LIMIT clause as a method of cost control », parce qu'il « doesn't affect the amount of data that is read ». Un LIMIT 10 ne rend pas ta requête moins chère.
# Estimer avant d'exécuter
bq query --dry_run --use_legacy_sql=false \
  "SELECT magasin_code, SUM(montant_ttc) FROM analytics.fct_commandes
   WHERE date_commande BETWEEN '2026-01-01' AND '2026-01-31' GROUP BY 1"

# Plafonner la facture d'une requête (10 Go)
bq query --maximum_bytes_billed=10000000000 --use_legacy_sql=false "SELECT ..."

Google range les facteurs de performance en six familles. Voici le libellé exact de la doc, et ce que tu cherches à réduire dans chacune :

Famille (libellé Google)Ce que tu réduis
Input data and data sources (I/O)Les octets lus par la requête
Communication between nodes (shuffling)Les octets échangés entre les étapes
ComputationLe travail de calcul (CPU)
Outputs (materialization)Les octets écrits
Capacity and concurrencyLa concurrence entre requêtes sur les slots
Query patternsLes mauvaises habitudes d'écriture SQL

Les quatre premières sont celles sur lesquelles tu agis directement en réécrivant une requête. Reprends ce vocabulaire dans ton prompt : le modèle s'y aligne et te rend une liste structurée au lieu d'un fourre-tout.


5. Expliquer une requête existante

Cas très fréquent : tu hérites d'une requête de 300 lignes, sans commentaire, écrite par quelqu'un qui est parti. L'IA est excellente ici, parce que la source de vérité (le SQL) est entièrement dans le prompt. Le risque d'hallucination est faible.

Demande une sortie en trois blocs : intention métier, étape par étape, pièges détectés. Le troisième bloc est le plus utile. Les pièges classiques à faire chercher :

  • un LEFT JOIN neutralisé par une condition sur la table de droite placée dans le WHERE au lieu du ON ;
  • COUNT(colonne) qui ignore les NULL, alors que COUNT(*) ne les ignore pas ;
  • UNION qui dédoublonne en silence là où UNION ALL était voulu ;
  • une jointure sur une clé non unique qui duplique les lignes et gonfle les sommes.

6. Traduire entre dialectes

Traduire PostgreSQL → BigQuery ou SQL Server → Snowflake est un bon usage, à condition de nommer explicitement les deux dialectes et de demander une liste des constructions non traduisibles.

Les points qui cassent en silence, à faire vérifier systématiquement :

  • Dates : DATE_TRUNC, DATEADD, EXTRACT n'ont pas les mêmes signatures.
  • Division : certains moteurs renvoient un entier, d'autres un flottant.
  • NULL dans les tris : NULLS FIRST / NULLS LAST n'est pas le défaut partout.
  • Sensibilité à la casse des identifiants.
  • Fenêtrage : QUALIFY existe sur BigQuery et Snowflake, pas sur PostgreSQL.

Toujours terminer par : exécute les deux requêtes sur le même jeu de données et compare le nombre de lignes et les sommes. Une traduction non comparée n'est pas une traduction validée.


🧰 Templates à copier-coller

Template A — Écrire une requête

Tu es ingénieur data senior, expert en [DIALECTE_SQL].

<schema>
[COLLE_ICI_LES_CREATE_TABLE_AVEC_COMMENTAIRES]
</schema>

<regles_metier>
- [REGLE_1_EX_LE_CA_EXCLUT_LES_ANNULATIONS]
- [REGLE_2_EX_EXERCICE_FISCAL_1ER_JUILLET_AU_30_JUIN]
</regles_metier>

<question>
[FORMULE_TA_QUESTION_EN_UNE_PHRASE]
</question>

Contraintes :
- Dialecte : [DIALECTE_SQL].
- Structure en CTE nommées, une par étape logique.
- Filtre obligatoire sur la colonne de partitionnement : [NOM_COLONNE_PARTITION].
- Granularité attendue du résultat : une ligne par [GRANULARITE].

Produis dans cet ordre :
1. <hypotheses> : toutes les hypothèses que tu as dû faire.
2. <sql> : la requête.
3. <verification> : une requête de contrôle simple qui permet de vérifier
   l'ordre de grandeur du résultat.

Si le schéma ne suffit pas, écris "information manquante : [...]" au lieu
d'inventer un nom de colonne.

Template B — Optimiser

Voici une requête [DIALECTE_SQL] qui met [DUREE_ACTUELLE] et lit [VOLUME_LU].

<requete>
[COLLE_LA_REQUETE]
</requete>

<schema>
[DDL_AVEC_PARTITIONNEMENT_ET_CLUSTERING]
</schema>

<plan_execution>
[COLLE_LE_PLAN_OU_ECRIS_"non disponible"]
</plan_execution>

Propose des optimisations classées dans cet ordre, avec le vocabulaire Google :
1. input data and data sources (I/O) : réduire les octets lus
2. communication between nodes (shuffling) : réduire les octets échangés
3. computation : réduire le travail de calcul
4. outputs (materialization) : réduire les octets écrits

Pour chaque proposition, donne : le changement, le gain attendu, et le risque
de changer le résultat. Marque clairement celles qui modifient la sémantique.
Ne prétends pas connaître le gain réel : indique comment je dois le mesurer.

Template C — Expliquer une requête héritée

<requete>
[COLLE_LA_REQUETE]
</requete>

Explique-la en trois parties :
1. <intention> : en 3 phrases maximum, ce que cette requête calcule, en langage métier.
2. <etapes> : un tableau | étape | ce qu'elle fait | granularité en sortie |.
3. <pieges> : tout ce qui peut produire un résultat surprenant
   (LEFT JOIN neutralisé par un WHERE, COUNT qui ignore les NULL,
   UNION qui dédoublonne, jointure qui duplique des lignes).

N'affirme rien qui ne soit pas visible dans la requête. Si le sens d'une
colonne est ambigu sans le schéma, dis-le.

Template D — Traduire un dialecte

Traduis cette requête de [DIALECTE_SOURCE] vers [DIALECTE_CIBLE].

<requete_source>
[COLLE_LA_REQUETE]
</requete_source>

Exigences :
- Conserve exactement la même sémantique, y compris le traitement des NULL
  et l'ordre de tri.
- Rends un tableau | construction source | équivalent cible | risque de
  différence de résultat |.
- Signale séparément, sous <non_traduisible>, toute construction sans
  équivalent direct, avec la solution de contournement proposée.
- Termine par la requête de comparaison que je dois lancer pour vérifier
  que les deux versions renvoient le même résultat.

A retenir

  • Sans DDL commenté dans le prompt, le modèle invente des colonnes : le schéma n'est pas optionnel.
  • Emballe schéma, règles métier et question dans des balises XML distinctes, et mets le schéma en haut.
  • Exige les hypothèses avant le SQL, et donne toujours au modèle le droit de dire « information manquante ».
  • Une optimisation proposée par l'IA n'est pas mesurée : valide par le plan d'exécution et par un contrôle de volumétrie.
  • Sur BigQuery : --dry_run pour estimer (estimation haute), maximum bytes billed pour plafonner, et LIMIT ne réduit pas le coût sur une table non clusterisée.
  • EXPLAIN ANALYZE existe sur PostgreSQL et MySQL, pas sur Snowflake ni sur BigQuery : nomme le bon outil de mesure selon ton moteur.

Sources

Corpus personnel de formation · genere le 26/09/2026 · source : 01-sql-et-texte-vers-sql.md