¿Responde la consulta del modelo a la pregunta?
Revisar el SQL que escribe un modelo es comparar la consulta con la pregunta antes de fiarte de ella. Una consulta puede funcionar sin error y responder a otra pregunta.
El módulo 4 enseña uniones y agrupaciones. Esta lección revisa el SQL que un modelo escribió para una pregunta y busca sus tres fallos más frecuentes.
Al final de esta lección
Nota
Puedo leer la consulta de un modelo y decir si responde a la pregunta que se hizo.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Cláusula | una parte de una consulta, como WHERE | |
| Filtro | una prueba que deja pasar solo algunas filas | returned = 0 |
| Duplicado | un mismo valor contado más de una vez | |
| DISTINCT | deja cada valor una sola vez | COUNT(DISTINCT member_id) |
| Columna de unión | la columna por la que se emparejan dos tablas |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Lee la pregunta antes que la consulta
Primero apunta lo que pide la pregunta. ¿Qué filas cuentan? ¿Se espera un número, una lista o un solo valor? ¿Hay que contar cada cosa una sola vez?
Después lee la consulta cláusula a cláusula. FROM y JOIN dicen de dónde salen las filas, WHERE se queda con una parte y SELECT dice qué se devuelve.
Ejemplo resuelto: trozos de la pregunta y cláusulas
| La pregunta dice | La consulta necesita | Cláusula |
|---|---|---|
| en el mes 3 | month = 3 | WHERE |
| socios distintos | DISTINCT member_id | SELECT |
| cuántos | COUNT | SELECT |
| títulos | una unión de loans con books por book_id | FROM y JOIN |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Tres fallos frecuentes de un modelo
Contar duplicados: COUNT(member_id) cuenta a Alba dos veces si tomó prestado dos veces. Un filtro olvidado: la consulta cuenta todos los préstamos, no solo los que siguen fuera.
La unión equivocada: loans.id = books.id empareja el préstamo 3 con el libro 3 solo porque llevan el mismo número. La unión tiene que pasar por loans.book_id.
Ejemplo resuelto: mal y bien
| Pregunta | Trozo erróneo | Trozo correcto |
|---|---|---|
| Socios distintos en el mes 3 | COUNT(member_id) | COUNT(DISTINCT member_id) |
| Libros que siguen prestados | Sin WHERE | WHERE returned = 0 |
| Títulos que tomó el socio 2 | ON l.id = b.id | ON l.book_id = b.id |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que una consulta que funciona sin error responde a la pregunta. Un error solo dice que el SQL está roto. Una unión equivocada o un filtro olvidado funcionan en silencio y dan una respuesta falsa.
Escribe el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' '// Una respuesta puede envolver la consulta en un bloque delimitado: nos quedamos con el interior.' 'function sqlOf(text) {' ' const parts = text.split(String.fromCharCode(96).repeat(3));' ' const inner = (parts.length > 2 ? parts[1] : text).trim();' ' return inner.toLowerCase().startsWith(\"sql\") ? inner.slice(3).trim() : inner;' '}' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el ayudante del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, 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(\"estado \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask, sqlOf };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la revisión de consultas, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask, sqlOf } = require(\"./model\");' 'const questions = [' ' { q: \"¿Cuántos socios distintos tomaron prestado un libro en el mes 3?\", needs: [\"DISTINCT\", \"month = 3\"],' ' a: \"SELECT COUNT(member_id) FROM loans WHERE month = 3\",' ' b: \"SELECT COUNT(DISTINCT member_id) FROM loans WHERE month = 3\" },' ' { q: \"¿Cuántos libros siguen prestados?\", needs: [\"returned = 0\"],' ' a: \"SELECT COUNT(*) FROM loans\",' ' b: \"SELECT COUNT(*) FROM loans WHERE returned = 0\" },' ' { q: \"¿Qué títulos ha tomado prestados el socio 2?\", needs: [\"book_id\", \"member_id = 2\"],' ' a: \"SELECT b.title FROM loans l JOIN books b ON l.id = b.id WHERE l.member_id = 2\",' ' b: \"SELECT b.title FROM loans l JOIN books b ON l.book_id = b.id WHERE l.member_id = 2\" },' '];' > review.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la revisión de consultas, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function squash(sql) { return sql.toUpperCase().split(/\s+/).join(\" \").trim(); }' 'function review(sql, needs) {' ' const text = squash(sql);' ' const problems = needs.filter(function (n) { return text.indexOf(squash(n)) < 0; })' ' .map(function (n) { return \"falta \" + n; });' ' if (/ON \w+\.ID = \w+\.ID\b/.test(text)) problems.push(\"une id con id\");' ' return problems.length ? problems.join(\", \") : \"parece correcta\";' '}' >> review.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la revisión de consultas, parte 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: \" + review(x.a, x.needs));' ' console.log(\" B: \" + review(x.b, x.needs));' ' });' ' const schema = \"loans(id, book_id, member_id, month, returned)\nmembers(id, name)\nloans.member_id -> members.id\";' ' const system = \"Responde con una sola consulta SELECT de SQLite y nada más.\";' ' const sample = \"SELECT COUNT(DISTINCT member_id) FROM loans WHERE month = 3;\";' ' const reply = await ask([{ role: \"system\", content: system },' ' { role: \"user\", content: schema + \"\nPregunta: \" + questions[0].q }], sample, 120);' ' const sql = sqlOf(reply.text);' ' console.log(\"el modelo escribió: \" + sql);' ' console.log(\" revisión: \" + review(sql, questions[0].needs));' '}' 'main();' >> review.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta la revisión
docker exec -w /home/node/project agent node review.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lo que imprime el Run
| Línea | Qué muestra |
|---|---|
| A: falta DISTINCT | Pregunta 1, la candidata A cuenta duplicados |
| A: falta returned = 0 | Pregunta 2, la candidata A no tiene filtro |
| A: falta book_id, une id con id | Pregunta 3, la candidata A une por la columna equivocada |
| B: parece correcta | Cada candidata B pasa la revisión |
| el modelo escribió: ... | La consulta del modelo para la pregunta 1, revisada igual, o una muestra grabada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.