クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

データベースのスキーマをプロンプトに入れる

このレッスンのあとで: モデルが SQL を書けるように、データベースのスキーマを短く説明できる。

65分のレッスンコマンド7件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

Text to SQL に必要な情報

Text to SQL とは、ふつうの言葉の質問を、言語モデルが SQL のクエリに変えることです。モデルはあなたのデータベースを見たことがありません。だから、説明していない表は、推測するしかありません。

モジュール 4 は SQL を教え、モジュール 63 はモデルに決まった形で答えさせました。このレッスンでは、表について、モデルに何を伝えるかを決めます。

このレッスンのゴール

メモ

モデルが SQL を書けるように、データベースのスキーマを短く説明できる。

キーワード

ことばいみれい
Text to SQLモデルが質問をクエリに変えること
スキーマデータベースの表と列
外部キー別の表の行を指す列loans.book_id
結合二つの表の行を、ある列で組み合わせること
メモ列の値の意味を言う一行

学生が入力するセルは、このプレビューでは空白です。

モデルには表と列と結合が要る

クエリを書くために、モデルには三つのものが要ります。表の名前、それぞれの表の列、そして表どうしの結合のしかたです。

結合を伝えないと、モデルは推測します。たとえば loans.id を books.id に結合する推測は、エラーなしで動き、違う本を返します。

例: 図書館のスキーマ

表列結合する先
booksid, title, yearなし
membersid, nameなし
loansid, book_id, member_id, month, returnedbook_id は books.id、member_id は members.id

学生が入力するセルは、このプレビューでは空白です。

メモが要る列もある

列の名前が、値の意味をいつも言うとは限りません。図書館の returned は、本が戻ると 1、貸し出し中は 0 です。

名前だけを読んだモデルは、returned = "いいえ" や returned IS NULL を試すかもしれません。分かりにくい列ごとに短いメモを一つ書けば、この推測を防げます。

例: メモが要る名前

列メモがないときの推測メモ
loans.returnedreturned = "いいえ"戻ると 1、貸し出し中は 0
loans.month2026-03-01 のような日付今年の月で、1 から 12
books.year買った年出版された年

学生が入力するセルは、このプレビューでは空白です。

よくある間違い

注意

モデルは自分のデータベースをもう知っている、と考える人が多いです。モデルは一度も見ていません。要求はどれも何もないところから始まるので、表、結合、メモを質問のたびに送りましょう。

短い説明は CREATE 文全体に勝る

CREATE TABLE 文をすべて貼り付けることもできます。表ごとに短い一行、次に結合とメモを並べれば、同じことをモデルが読みやすい形で言えます。

表が四十あるデータベースでは、質問に要る表だけを送ります。言葉が少ないほど費用が下がり、読み違いも減ります。

助っ人 model.js を書く、その 1

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' '// 応答がクエリを囲みで包んでいたら、内側だけを残す。' '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"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

助っ人 model.js を書く、その 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(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask, sqlOf };' >> model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

小さな図書館のデータを書く、その 1

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { DatabaseSync } = require(\"node:sqlite\");' '// 作り話の図書館: 本が六冊、会員が四人、貸し出しが九件。' 'const BOOKS = [[1, \"塩の道\", 2019], [2, \"ガラスの港\", 2021], [3, \"雨の地図\", 2015],' '  [4, \"静かなエンジン\", 2023], [5, \"七つのやかん\", 2018], [6, \"正午の北\", 2022]];' 'const MEMBERS = [[1, \"上田\"], [2, \"佐藤\"], [3, \"高橋\"], [4, \"中村\"]];' '// id, book_id, member_id, month, returned(1 は返却済み、0 は貸し出し中)' '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"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

小さな図書館のデータを書く、その 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"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

スキーマを説明するスクリプト、その 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();' 'const tables = db.prepare(\"SELECT name, sql FROM sqlite_master WHERE type = ? ORDER BY name\").all(\"table\");' 'function brief(t) {' '  const cols = db.prepare(\"SELECT name, type FROM pragma_table_info(?)\").all(t.name)' '    .map(function (c) { return c.name + \" \" + c.type; });' '  return t.name + \"(\" + cols.join(\", \") + \")\";' '}' 'const links = [];' 'tables.forEach(function (t) {' '  db.prepare(\"SELECT * FROM pragma_foreign_key_list(?)\").all(t.name).forEach(function (k) {' '    links.push(t.name + \".\" + k.from + \" -> \" + k.table + \".\" + k.to);' '  });' '});' > schema.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

スキーマを説明するスクリプト、その 2

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const notes = [\"loans.returned は、本が戻ると 1、貸し出し中は 0\", \"loans.month は今年の月で、1 から 12\"];' 'const schema = tables.map(brief).concat(links, notes).join(\"\n\");' 'const whole = tables.map(function (t) { return t.sql; }).join(\"\n\");' 'console.log(schema);' 'console.log(\"CREATE 文全体: \" + whole.length + \" 文字、メモつきの短い説明: \" + schema.length + \" 文字\");' 'async function main() {' '  const question = \"3 月の貸し出しは何件ですか。\";' '  const system = \"SQLite の SELECT 文を一つだけ答えてください。ほかには何も書かないでください。\";' '  const sample = \"SELECT COUNT(*) FROM loans WHERE month = 3;\";' '  const reply = await ask([{ role: \"system\", content: system },' '    { role: \"user\", content: schema + \"\n質問: \" + question }], sample, 120);' '  console.log(\"モデルが書いたクエリ: \" + sqlOf(reply.text));' '  console.log(\"まだ確かめていない: 確かめるのはレッスン 2 から 5\");' '}' 'main();' >> schema.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

スキーマを説明して質問する

コマンド
docker exec -w /home/node/project agent node --experimental-sqlite --no-warnings schema.js

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

Run の出力

行意味
books(id INTEGER, title TEXT, year INTEGER)表ごとの短い一行
loans.book_id -> books.id結合ごとの一行。外部キーから読む
loans.returned は、本が戻ると 1、貸し出し中は 0あなたが手で書いたメモ
CREATE 文全体: 283 文字、メモつきの短い説明: 278 文字二つの形の長さ
モデルが書いたクエリ: SELECT COUNT(*) FROM loans WHERE month = 3;クエリ。モデルが答えなければ記録済みサンプル

学生が入力するセルは、このプレビューでは空白です。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。