Ejecuta la consulta con una respuesta conocida
Comprobar SQL ejecutándolo es probar la consulta de un modelo con unos datos pequeños cuya respuesta has calculado a mano. Leer la consulta encuentra algunos fallos; ejecutarla encuentra los que parecen correctos en la página.
El módulo 43 comprueba la consulta de una persona por sus filas. Esta lección comprueba las candidatas de un modelo y se pregunta si los datos separan lo correcto de lo erróneo.
Al final de esta lección
Nota
Puedo comprobar la consulta de un modelo ejecutándola con datos de respuesta conocida.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Respuesta conocida | el resultado calculado a mano |
| Candidata | una consulta propuesta como respuesta |
| Filas del resultado | lo que devuelve una consulta |
| Datos de prueba | datos pequeños y fijos que se guardan para comprobar |
| Separar | dar resultados distintos a la consulta correcta y a la errónea |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Compara las filas con una respuesta conocida
Calcula la respuesta a mano con unos datos de prueba pequeños. Nueve préstamos se cuentan en papel. Luego ejecuta cada candidata y compara sus filas con las tuyas.
Hay tres veredictos. Pasa significa que las filas coinciden. Filas incorrectas significa que se ejecutó y dio otra cosa. Error significa que SQLite la rechazó.
Ejemplo resuelto: respuestas conocidas de la biblioteca
| Pregunta | Calculado a mano | Respuesta conocida |
|---|---|---|
| Socios distintos en el mes 3 | Préstamos 1 a 4: Alba, Alba, Bruno, Carmen | 3 |
| Libros que siguen prestados | Los préstamos 3, 5, 6, 8 y 9 tienen returned = 0 | 5 |
| Títulos que tomó el socio 2 | Préstamos 3, 6 y 8: libros 3, 4 y 5 | Mapa de la lluvia, Motores en calma, Siete teteras |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El orden solo importa si se pide
Una lista de títulos en cualquier orden responde a «qué títulos». Compara copias ordenadas de las dos listas: otro orden también pasa.
Si la pregunta dice «el más reciente primero», el orden forma parte de la respuesta. Entonces compara las filas tal como llegan.
Datos que separan lo correcto de lo erróneo
Pregunta cuántos préstamos del mes 5 siguen fuera. Los dos préstamos del mes 5 están fuera, así que una consulta sin filtro sobre returned también da 2.
Estos datos no separan las dos consultas. Añade una fila que las separe, como un préstamo devuelto en el mes 5, y vuelve a comprobar.
Un error frecuente
Advertencia
Mucha gente cree que una candidata que pasa con los datos de prueba es correcta. Es correcta para esos datos. Comprueba que los datos de prueba dan otra respuesta a la consulta errónea probable y solo entonces fíate del pase.
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 base de la biblioteca, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { DatabaseSync } = require(\"node:sqlite\");' '// Una biblioteca inventada: seis libros, cuatro socios, nueve préstamos.' 'const BOOKS = [[1, \"La calle de la sal\", 2019], [2, \"Puerto de cristal\", 2021], [3, \"Mapa de la lluvia\", 2015],' ' [4, \"Motores en calma\", 2023], [5, \"Siete teteras\", 2018], [6, \"Al norte del mediodía\", 2022]];' 'const MEMBERS = [[1, \"Alba\"], [2, \"Bruno\"], [3, \"Carmen\"], [4, \"Diego\"]];' '// id, book_id, member_id, month, returned (1 devuelto, 0 aún fuera)' 'const LOANS = [[1, 1, 1, 3, 1], [2, 2, 1, 3, 1], [3, 3, 2, 3, 0], [4, 1, 3, 3, 1], [5, 1, 1, 4, 0],' ' [6, 4, 2, 4, 0], [7, 2, 3, 4, 1], [8, 5, 2, 5, 0], [9, 2, 4, 5, 0]];' > library.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 base de la biblioteca, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function put(db, table, rows) {' ' const marks = rows[0].map(function () { return \"?\"; }).join(\", \");' ' const insert = db.prepare(\"INSERT INTO \" + table + \" VALUES (\" + marks + \")\");' ' rows.forEach(function (row) { insert.run(...row); });' '}' 'function open(file) {' ' const db = new DatabaseSync(file || \":memory:\");' ' db.exec(\"CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, year INTEGER)\");' ' db.exec(\"CREATE TABLE members (id INTEGER PRIMARY KEY, name TEXT)\");' ' db.exec(\"CREATE TABLE loans (id INTEGER PRIMARY KEY, book_id INTEGER REFERENCES books(id), \"' ' + \"member_id INTEGER REFERENCES members(id), month INTEGER, returned INTEGER)\");' ' put(db, \"books\", BOOKS);' ' put(db, \"members\", MEMBERS);' ' put(db, \"loans\", LOANS);' ' return db;' '}' 'module.exports = { open };' >> library.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 script de comprobación, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask, sqlOf } = require(\"./model\");' 'const { open } = require(\"./library\");' 'const db = open();' 'function rowsOf(sql) {' ' try {' ' return db.prepare(sql).all().map(function (r) { return JSON.stringify(Object.values(r)); }).sort();' ' } catch (error) { return null; }' '}' 'function check(sql, want) {' ' const got = rowsOf(sql);' ' if (got === null) return \"error\";' ' const wanted = want.map(function (r) { return JSON.stringify(r); }).sort();' ' return got.join(\"|\") === wanted.join(\"|\") ? \"pasa\" : \"filas incorrectas \" + got.join(\" \");' '}' > check.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 script de comprobación, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const L = \"SELECT COUNT(*) FROM loans WHERE \";' 'const T = \"SELECT b.title FROM loans l JOIN books b ON \";' 'const cases = [' ' { want: [[3]], sql: [\"SELECT COUNT(member_id) FROM loans WHERE month = 3\",' ' \"SELECT COUNT(DISTINCT member_id) FROM loans WHERE month = 3\",' ' \"SELECT COUNT(DISTINCT member) FROM loans WHERE month = 3\"] },' ' { want: [[5]], sql: [\"SELECT COUNT(*) FROM loans\", L + \"returned = 0\", L + \"returned = 1\"] },' ' { want: [[\"Mapa de la lluvia\"], [\"Motores en calma\"], [\"Siete teteras\"]],' ' sql: [T + \"l.id = b.id WHERE l.member_id = 2\", T + \"l.book_id = b.id WHERE l.member_id = 2\",' ' T + \"l.book_id = b.id WHERE l.member_id = 2 ORDER BY b.title DESC\"] },' ' { want: [[2]], sql: [L + \"month = 5 AND returned = 0\", L + \"month = 5\"] },' '];' >> check.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 script de comprobación, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' cases.forEach(function (c, i) {' ' c.sql.forEach(function (sql, j) { console.log((i + 1) + \"abc\"[j] + \": \" + check(sql, c.want)); });' ' });' ' db.exec(\"UPDATE loans SET returned = 1 WHERE id = 8\");' ' console.log(\"cuando vuelve el préstamo 8, la respuesta a 4 es 1:\");' ' cases[3].sql.forEach(function (sql, j) { console.log(\"4\" + \"ab\"[j] + \": \" + check(sql, [[1]])); });' ' db.exec(\"UPDATE loans SET returned = 0 WHERE id = 8\");' ' await live();' '}' >> check.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 script de comprobación, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function live() {' ' const q = \"loans(id, book_id, member_id, month, returned)\nPregunta: ¿Cuántos socios distintos tomaron prestado un libro en el mes 3?\";' ' const sample = \"SELECT COUNT(DISTINCT member_id) FROM loans WHERE month = 3;\";' ' const reply = await ask([{ role: \"system\", content: \"Responde con una sola consulta SELECT de SQLite y nada más.\" },' ' { role: \"user\", content: q }], sample, 120);' ' const sql = sqlOf(reply.text);' ' console.log(\"el modelo escribió: \" + sql);' ' console.log(\" comprobada con datos conocidos: \" + check(sql, [[3]]));' '}' 'main();' >> check.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 cada candidata con datos conocidos
docker exec -w /home/node/project agent node --experimental-sqlite --no-warnings check.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 |
|---|---|
| 1a: filas incorrectas [4] | Alba contada dos veces |
| 1c: error | Ninguna columna se llama member |
| 2c: filas incorrectas [4] | Se contaron los libros devueltos |
| 3a: filas incorrectas ["Al norte del mediodía"] ["Mapa de la lluvia"] | La unión de id con id |
| 3c: pasa | Otro orden también pasa |
| 4a: pasa y 4b: pasa | Los datos no las separan |
| 4b: filas incorrectas [2], cuando vuelve el préstamo 8 | Una fila nueva las separa |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.