▍AI Dev Lab
SQL道場

SQL道場 ができるまで — SQLite WASM でバックエンド無しにする

環境構築不要でSQLを練習できるサービス。sql.js(SQLite WASM)のシングルトン設計・実行結果検証による採点ロジック・CmdDojoアーキテクチャの再利用を全公開。

·decision改善·stage検証中

「SQLって何から始めたらいいの?」

プログラミング学習者の多くがここで詰まります。「とりあえずMySQLをインストールして……」と始めた瞬間に、環境構築の壁にぶつかって諦める。データベースを学ぶ前に別の問題で挫折する、そのパターンを何とかしたかった。

ブラウザを開くだけでSQLを打てたら、その壁はなくなる。

それが SQL道場 の出発点です。CmdDojo(Linuxコマンド練習)の設計パターンを流用し、SQLite WASMでバックエンドなしのSQLエディタを実装しました。


#🎯 なぜSQL練習サービスなのか

#CmdDojoからの発想

CmdDojoを作ったとき、「このアーキテクチャ、SQLにも使えるんじゃないか」と気づきました。

CmdDojoの構造は:

  • 仮想ファイルシステム(TypeScriptで実装)
  • コマンドエンジン(文字列 → 実行 → 出力)
  • 採点ロジック(validate関数)
  • レッスン・コース定義

SQL道場はこれを:

  • サンプルDB(sql.jsで初期化)
  • SQLエンジン(文字列 → sql.jsで実行 → 結果テーブル)
  • 採点ロジック(結果セットを検証するvalidate関数)
  • レッスン・コース定義

に置き換えるだけでした。

#SEO的な狙い

「SQL 練習」「SQL 入門」「SQLite チュートリアル」「基本情報 データベース」——これらは検索需要が大きく、かつ既存の学習コンテンツがやや古いものが多い。

CmdDojo・SQL道場・(将来的には)Git道場を合わせて「道場シリーズ」としてブランド化できれば、「ブラウザで学べる技術練習サイト」という認知を作れます。


#🏗️ 技術選定:なぜsql.jsか

SQLをブラウザで動かす選択肢はいくつかあります。

選択肢メリットデメリット
sql.js安定・実績あり・SQLite互換WASMファイル(650KB)が重い
@sqlite.org/sqlite-wasm公式・最新セットアップが複雑
absurd-sqlIndexedDB永続化できるメンテが止まっている
バックエンドDBフル機能サーバーコストが発生

sql.jsを選んだ理由は実績と安定性です。GitHubスターも多く、Next.jsとの組み合わせ事例も豊富。WASMファイルのサイズは初回ロード時のみ気になる程度で、一度キャッシュされれば問題ありません。

#Next.jsでのWASM配信方法

sql.jsはWASMファイルを動的に読み込みます。Next.jsではこれを public/ に配置して配信しました。

bash
# インストール後にWASMをpublicにコピー
cp node_modules/sql.js/dist/sql-wasm.wasm public/sql-wasm/
typescript
// SqlEngine.ts
const SQL = await initSqlJs({
 locateFile: => "/sql-wasm/sql-wasm.wasm"
})

locateFile で配信パスを明示的に指定するのがポイント。デフォルトのCDNパスはNext.jsのCSPと衝突することがあります。


#🔧 実装の詳細

#DBインスタンスの管理

sql.jsのDBインスタンスをReactのstateに入れると、再レンダリングのたびに問題が起きます。ZustandのstoreにDB objectを入れるのもNGです(シリアライズできないため)。

解決策はモジュールレベル変数:

typescript
// SqlEngine.ts — モジュールスコープでシングルトン管理
let dbInstance: any = null

export async function getDatabase(initSql: string): Promise<any> {
 const SQL = await getSql
 if (!dbInstance) {
 dbInstance = new SQL.Database
 dbInstance.run(initSql)
 }
 return dbInstance
}

export function resetDatabase {
 dbInstance = null // レッスン切り替え時にリセット
}

Reactのライフサイクルに依存せず、モジュールの初期化時に一度だけDBを作って使い回します。

#結果の表示形式

CmdDojoはテキスト出力でしたが、SQL道場はテーブル表示が必要です。sql.jsの実行結果は:

typescript
interface SqlResult {
 columns: string[]
 values: (string | number | null)[][]
 rowCount: number
}

この構造をそのまま <table> タグに展開します。ポイントは overflow-x: auto のラッパー — 列が多い場合に横スクロールできるようにしました。

#採点ロジック

CmdDojoでは「入力文字列が正しいか」を検証していましたが、SQLは「同じ結果を出すSQLが複数ある」ため、文字列一致では採点できません。

解決策:実行結果を検証するvalidate関数

typescript
// lesson-01-select.ts
{
 id: "sql-01-ex-02",
 instruction: "name と price の列だけ取得してください",
 validate: (result) => {
 if (!result) return false
 return (
 result.rowCount === 10 &&
 result.columns.length === 2 &&
 result.columns.includes("name") &&
 result.columns.includes("price")
 )
 },
}

SELECT name, price FROM products でも SELECT price, name FROM products でも正解にできます。「行数・列数・含まれるカラム名」で柔軟に判定しています。

#サンプルデータベースの設計

学習に使うデータは「身近で直感的」であることが重要です。

sql
-- 3テーブル構成
products (商品: id, name, category, price, stock)
customers (顧客: id, name, city, age)
orders (注文: id, customer_id, product_id, quantity, order_date)

電子機器・家具・文房具の商品、東京・大阪・名古屋の顧客。GROUP BY を練習するとき「カテゴリ別の平均価格」「都市別の顧客数」といった自然な集計ができます。JOIN練習では「顧客の注文一覧」が作れる。


#🎨 UIデザインの考え方

#CmdDojoとの差別化

SQL道場とCmdDojoは同じ「左:解説 / 右:操作画面」のレイアウトですが、操作パネルが全く異なります。

項目CmdDojoSQL道場
入力欄1行のターミナル入力複数行テキストエリア
出力テキスト行表(テーブル)
実行EnterキーCtrl+Enter / ボタン
アクセントカラーTeal (#4ec9b0)Blue (#4db6f0)

SQLは複数行で書くことが多いので、<textarea> にしました。Ctrl+Enterで実行できるようにして、キーボードから手を離さずに操作できます。

┌────────────────┬────────────────────────┐
│ │ SELECT name, price │
│ 解説パネル │ FROM products │
│ │ WHERE price > 10000; │
│ コマンド概念 ├────────────────────────┤
│ オプション表 │ ▶ 実行 │
│ 例文 ├────────────────────────┤
│ │ name | price │
│ │ ノートPC | 98000 │
│ │ モニター | 45000 │
│ ├────────────────────────┤
│ │ 練習問題トラッカー │
└────────────────┴────────────────────────┘

#⚡ 開発スピードについて

SQL道場はCmdDojoから3日後に公開しました。

これが可能だった理由:

  1. アーキテクチャの再利用 — レッスン・コース・採点の構造はほぼ同じ
  2. CSSの流用 — cmd-dojo.module.cssをベースに色を変えるだけ
  3. ページ構成の複製 — ルーティング・layout・generateMetadataのパターンが確立済み
  4. AIエージェントによる実装 — 設計意図を伝えれば、実装はエージェントが担当

「1つ作ると2つ目は半分の時間で作れる」を実感しました。


#📊 コース設計の考え方

#入門コースの5レッスン構成

レッスンテーマ学べること
01SELECT の基本全件取得・列指定
02WHERE で絞り込む条件指定・AND/OR/LIKE/BETWEEN
03並び替えと件数制限ORDER BY / LIMIT
04集計関数COUNT / SUM / AVG / MAX / MIN
05GROUP BYグループ集計・HAVING

この5つは、データを取得・絞り込み・集計するための基礎です。業務全体の何割をカバーするかは、仕事内容やデータ構造によって変わります。

#現在の教材 (2026年10月)

入門・中級・上級・実務データ分析の4コース、21レッスン・合計62問を公開しています。JOIN・サブクエリ・ウィンドウ関数に加え、実務データ分析では90日分・563行の練習用売上データで月次集計や構成比、店舗別上位抽出を練習できます。

教材一覧はSQL練習問題一覧、実務向けの入口は実務データ分析SQLコースです。SQLiteで動作する教材であり、他のデータベース製品と構文や関数がすべて同じという意味ではありません。


#利用料金と現在の提供範囲

公開中の4コースは無料です。有料コースや法人プランを販売しているわけではありません。広告収益や学習成果も、教材を公開しただけで保証できるものではありません。

環境構築をせずにクエリを実行できるため、授業や自主学習の補助として使えます。一方、実際の業務データを接続する機能や、データベース管理者向けの運用研修は提供していません。


#複数行SQLの編集体験を改善

2026年10月の改善では、行番号、SQLの色分け、選択した複数行の字下げ・解除、元に戻す・やり直す操作を追加しました。入力欄を大型エディタライブラリに置き換えず、標準のtextareaに表示専用の色分けを重ねています。追加の有料APIや依存パッケージは使っていません。

Tabで字下げ、Shift+Tabで解除、CtrlまたはCommand+Enterで実行できます。入力欄からキーボードで移動する場合はEscを押してからTabを押します。スマートフォン向けには字下げと編集履歴のボタンも用意しました。

表示側はSQLを実行せず、HTMLとして解釈しません。文字列、コメント、未閉じの引用符、日本語の入力をそのまま維持します。入力欄の横・縦スクロールに色分けと行番号を同期させ、日本語変換中は実行や字下げを割り込ませない設計にしました。色分けは構文エラー検査やSQLの自動修正ではありません。

検証では62問の判定に加え、SQLiteの実行結果、複数行の選択と取り消し、長いSQLのスクロールを確認します。日本語変換中のイベントを使った自動テストも行いますが、すべてのOS・IME製品の実操作を保証するものではありません。

#実務の順位は、集計する単位から考える

店舗別ランキングの3問を追加しました。売上明細の大きな1行ではなく、月・店舗・商品で合計してから順位を付け、最後に上位を抽出します。全体のLIMITで3行に切るだけでは、店舗ごとの上位3商品にはなりません。

同率順位には既存データの2026-03-15を使います。東京本店の商品5と10はともに105,000円、商品1は98,000円です。RANKでは1・1・3、DENSE_RANKでは1・1・2となるため、金額の上位2段階を残すには後者を選びます。順位に商品IDを混ぜると同率がなくなる一方、件数固定のROW_NUMBERには決め方として役立ちます。順位の基準と表示順を分けて説明しました。定義はSQLite公式のウィンドウ関数で確認できます。

新3問の採点は、件数だけでなく全行の店舗・商品・金額・順位、指定した列名と表示順を比較します。期待値はSQLの模範解答からコピーせず、元の563行をJavaScriptで集計・順位付けした結果と照合しました。店舗・月の区切り忘れ、RANKとDENSE_RANKの取り違え、同率への余計なキー、集計前のLIMIT、1セルだけの誤りも検査します。別構成でも同じ結果なら正解で、SQL構文そのものを強制する採点ではありません。

#全62問を、結果全体で採点する

順位以外には、件数や一部の値だけで判定する問題が残っていました。例えば1,000〜10,000円の商品を求める課題で、98,000円の商品を含む2行でも正解になっていました。残る56問も、練習データをJavaScriptで絞り込み・結合・集計した期待値と、全行・全列を比較する方式へ揃えました。

順序を指定していない問題は行を並べ替えても正解です。集計列の別名が指定されていなければ別名も自由にし、指定されている列名は確認します。判定対象の列と行順は課題の下にも表示します。NULLを0や文字列に置き換える答え、重複行で欠けた行を埋める答え、値が違うのに件数だけ合う答えは通しません。小数の計算誤差は絶対値0.0000001以内に限り許容し、丸めの桁数が曖昧だった3問も明確にしました。

全62問の正解例に加え、1,774セルそれぞれの改変、行の欠落・追加、誤った集計・結合・移動平均・構成比・前月比と、別構成の正しいSQLを検証します。これは固定の練習データに対する結果照合です。同じ結果を返すハードコードの検出や、未知のデータでもそのSQLが正しいことの証明、過去の保存成績の再認証はしません。


#まとめ

#重いSQLを中止して、教材だけを戻す

SQL実行は専用のWeb Workerへ移しました。処理が終わらないSQLでも画面側の操作を続けられ、「実行を中止」でWorkerを終了して教材DBを初期化できます。「教材リセット」も同じ教材復元を行います。UPDATEやDROPなどで変更した教材データは失われますが、入力SQL・編集履歴・学習進捗は残します。別レッスンへ移るときはそのレッスン用に教材を初期化します。

初期化失敗は待機表示のまま放置せず、画面に理由と再試行を出します。起動は60秒、実行は30秒で打ち切り、結果が1000行を超える場合は切り捨てて採点せずエラーにします。SQLエラーは訂正してそのまま再実行できます。BLOB表示は非対応を明示します。端末のメモリを無制限に保護できる仕組みや、あらゆるSQLの安全性を保証するものではありません。

SQL道場は「環境構築なしにSQLを練習できる」という明確なUXと、SEO的に積み上がるコンテンツ構造を持ったサービスです。

技術的にはsql.js(SQLite WASM)によるバックエンド不要の設計と、実行結果を検証するvalidate関数による柔軟な採点ロジックが肝です。

CmdDojoのアーキテクチャを流用することで短期間での開発を実現しました。「道場シリーズ」として展開することで、ブランドとSEO資産を積み重ねていく予定です。

[ ./next_action ]

読んだら、 SQL道場 を実際に動かす。

この開発ログは SQL道場 をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
RoomWeave

家具の自動配置をつくる:探索と通路判定の設計

家具配置シミュレーションの開発記録。複数の配置候補を探索し、10cm格子と指定通路幅で入口からの到達を確認する設計、探索の限界、操作画面を固めないWorkerの使い方を解説します。

read log →
TurnStone

TurnStone ができるまで — リバーシ AI の読みを盤に出す

リバーシ AI 対戦の開発ログ。 評価関数を 1 つの数値に潰さず 位置 ・ 打てる手 ・ 角 ・ 確定石 ・ 石数 の内訳で返す形にし、 置けるマス全部に評価値を重ねた記録です。 全手の点数を正しく出すために、 根での枝刈りを捨てた話も書いています。

read log →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — ブラウザ内で完結する embeddings 意味検索

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →