La requête du modèle répond-elle à la question ?
Relire du SQL écrit par un modèle, c’est comparer la requête à la question avant de lui faire confiance. Une requête peut tourner sans erreur et répondre à une autre question.
Le module 4 enseigne les jointures et les regroupements. Cette leçon relit le SQL qu’un modèle a écrit pour une question, et cherche ses trois défauts les plus fréquents.
À la fin de cette leçon
Remarque
Je peux relire la requête d’un modèle et dire si elle répond à la question posée.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Clause | une partie d’une requête, comme WHERE | |
| Filtre | un test qui ne garde que certaines lignes | rendu = 0 |
| Doublon | une même valeur comptée plusieurs fois | |
| DISTINCT | garde chaque valeur une seule fois | COUNT(DISTINCT membre_id) |
| Colonne de jointure | la colonne sur laquelle on apparie deux tables |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Lire la question avant la requête
Listez d’abord ce que demande la question. Quelles lignes comptent ? Attend-on un nombre, une liste ou une seule valeur ? Faut-il compter chaque chose une seule fois ?
Lisez ensuite la requête clause par clause. FROM et JOIN disent d’où viennent les lignes, WHERE en garde une partie et SELECT dit ce qui revient.
Exemple détaillé : morceaux de question et clauses
| La question dit | La requête a besoin de | Clause |
|---|---|---|
| au mois 3 | mois = 3 | WHERE |
| membres différents | DISTINCT membre_id | SELECT |
| combien | COUNT | SELECT |
| titres | une jointure de prets vers livres sur livre_id | FROM et JOIN |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Trois défauts fréquents chez un modèle
Compter des doublons : COUNT(membre_id) compte Amina deux fois si elle a emprunté deux fois. Un filtre oublié : la requête compte tous les prêts, pas seulement ceux encore dehors.
La mauvaise jointure : prets.id = livres.id apparie le prêt 3 au livre 3 juste parce qu’ils portent le même numéro. La jointure doit passer par prets.livre_id.
Exemple détaillé : faux et juste
| Question | Morceau faux | Morceau juste |
|---|---|---|
| Membres différents au mois 3 | COUNT(membre_id) | COUNT(DISTINCT membre_id) |
| Livres encore dehors | Pas de WHERE | WHERE rendu = 0 |
| Titres empruntés par le membre 2 | ON p.id = l.id | ON p.livre_id = l.id |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur courante
Attention
Beaucoup pensent qu’une requête qui tourne sans erreur répond à la question. Une erreur veut seulement dire que le SQL est cassé. Une mauvaise jointure ou un filtre oublié tournent sans bruit et donnent une réponse fausse.
Écrire l’assistant du modèle, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Les jetons d’une réponse, ou null si elle n’en donne pas.' 'function compter(donnees, nom) {' ' return donnees.usage && typeof donnees.usage[nom] === \"number\" ? donnees.usage[nom] : null;' '}' '// Une réponse peut entourer la requête d’un bloc délimité : on garde l’intérieur.' 'function sqlDe(texte) {' ' const morceaux = texte.split(String.fromCharCode(96).repeat(3));' ' const dedans = (morceaux.length > 2 ? morceaux[1] : texte).trim();' ' return dedans.toLowerCase().startsWith(\"sql\") ? dedans.slice(3).trim() : dedans;' '}' > model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire l’assistant du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function demander(messages, echantillon, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"statut \" + res.status);' ' const d = await res.json();' ' return { texte: d.choices[0].message.content, entree: compter(d, \"prompt_tokens\"), sortie: compter(d, \"completion_tokens\"), enregistre: false };' ' } catch (erreur) {' ' console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' ' return { texte: echantillon, entree: null, sortie: null, enregistre: true };' ' }' '}' 'module.exports = { demander, sqlDe };' >> model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire la grille de relecture, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander, sqlDe } = require(\"./model\");' 'const questions = [' ' { q: \"Combien de membres différents ont emprunté un livre au mois 3 ?\", besoins: [\"DISTINCT\", \"mois = 3\"],' ' a: \"SELECT COUNT(membre_id) FROM prets WHERE mois = 3\",' ' b: \"SELECT COUNT(DISTINCT membre_id) FROM prets WHERE mois = 3\" },' ' { q: \"Combien de livres sont encore dehors ?\", besoins: [\"rendu = 0\"],' ' a: \"SELECT COUNT(*) FROM prets\",' ' b: \"SELECT COUNT(*) FROM prets WHERE rendu = 0\" },' ' { q: \"Quels titres le membre 2 a-t-il empruntés ?\", besoins: [\"livre_id\", \"membre_id = 2\"],' ' a: \"SELECT l.titre FROM prets p JOIN livres l ON p.id = l.id WHERE p.membre_id = 2\",' ' b: \"SELECT l.titre FROM prets p JOIN livres l ON p.livre_id = l.id WHERE p.membre_id = 2\" },' '];' > relire.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire la grille de relecture, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function tasser(sql) { return sql.toUpperCase().split(/\s+/).join(\" \").trim(); }' 'function relire(sql, besoins) {' ' const texte = tasser(sql);' ' const defauts = besoins.filter(function (n) { return texte.indexOf(tasser(n)) < 0; })' ' .map(function (n) { return \"manque \" + n; });' ' if (/ON \w+\.ID = \w+\.ID\b/.test(texte)) defauts.push(\"jointure id sur id\");' ' return defauts.length ? defauts.join(\", \") : \"semble juste\";' '}' >> relire.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire la grille de relecture, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' questions.forEach(function (x, i) {' ' console.log((i + 1) + \" \" + x.q);' ' console.log(\" A : \" + relire(x.a, x.besoins));' ' console.log(\" B : \" + relire(x.b, x.besoins));' ' });' ' const schema = \"prets(id, livre_id, membre_id, mois, rendu)\nmembres(id, nom)\nprets.membre_id -> membres.id\";' ' const systeme = \"Répondez par une seule requête SELECT pour SQLite, et rien d’autre.\";' ' const echantillon = \"SELECT COUNT(DISTINCT membre_id) FROM prets WHERE mois = 3;\";' ' const reponse = await demander([{ role: \"system\", content: systeme },' ' { role: \"user\", content: schema + \"\nQuestion : \" + questions[0].q }], echantillon, 120);' ' const sql = sqlDe(reponse.texte);' ' console.log(\"le modèle a écrit : \" + sql);' ' console.log(\" relecture : \" + relire(sql, questions[0].besoins));' '}' 'main();' >> relire.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer la grille de relecture
docker exec -w /home/node/project agent node relire.jsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Ce que le Run affiche
| Ligne | Ce qu’elle montre |
|---|---|
| A : manque DISTINCT | Question 1, la candidate A compte des doublons |
| A : manque rendu = 0 | Question 2, la candidate A n’a pas de filtre |
| A : manque livre_id, jointure id sur id | Question 3, la candidate A joint sur la mauvaise colonne |
| B : semble juste | Chaque candidate B passe la relecture |
| le modèle a écrit : ... | La requête du modèle pour la question 1, relue de la même façon, ou un échantillon enregistré |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.