▍AI Dev Lab
SQL道場

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

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

·decision改善·stage検証中

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

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

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

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


#完了した教材を、もう一度練習する

完了画面から同じ教材を最初から再挑戦できるようにしました。途中の画面でも確認付きでやり直せます。今回の課題と得点だけを戻し、過去の最高完了得点と他教材の記録は残します。教材DBも初期化します。再挑戦中に再読み込みしても、以前の完了状態へ戻らず今回の続きから再開します。

得点は保存済みの合計に加算せず、今回達成した課題の配点から計算します。同じ課題の重複完了や未知の課題では増えません。全課題達成時に結果と最高完了得点を一度に確定します。現在の採点は誤答やヒントで減点せず、全課題を正解すれば配点合計に達します。低い点数の完了結果を作るために、架空の減点ルールは追加していません。

以前の保存キーと外側の形式を維持し、旧完了記録の得点を最高完了得点へ引き継ぎます。課題順序・配点・保存形式・新しい教材revisionを検査し、不正データは元のまま残して操作を止めます。個別初期化と全体初期化の影響は確認文で区別し、壊れた記録の最高点まで保持できるとは約束しません。保存容量不足では画面内だけの状態であることを表示して再試行できます。再挑戦の保存に失敗した場合は、以前の状態を消して成功したふりをしません。

既存の暗いSQLコンソールと青い操作色を維持し、保存状況と再挑戦は小さなパネルへまとめています。SQL入力・結果・教材DBへの変更は保存しないので、再読み込みではDBが初期化されます。端末間同期・複数タブ競合の解決・学習結果の認証ではありません。

#🎯 なぜ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 →
ShadowTwin

立体パズルの別解を作る:同じ影と最少の積み木

ShadowTwinの開発記録。3方向の図から別々の最少配置を2つ探す仕組み、5問の独立全探索、スマホの横並び比較、保存・再編集と入力検査を解説します。実照明の計算や能力診断ではありません。

read log →
SlitCard

スリットアニメーションを作る:4コマの合成と透明原稿の設計

SlitCardの開発記録。4コマの帯を交互に合成し、すき間と黒帯で別のコマを見せる仕組みを解説。同縮尺の台紙・透明シートSVG、50mm確認線、手動プレビュー・初期非表示、再編集と実出力検査を整理します。

read log →
WindowLight

日当たりシミュレーションを作る:窓の光を床へ投影する幾何モデル

WindowLightの開発記録。太陽位置近似から窓の4隅を床へ投影し、凸多角形を部屋で切り取る設計を解説。国立天文台の16時刻との限定照合、大気差・低高度・実際の採光との違い、SVGと設定の再編集を整理します。

read log →
GridLights

電源構成の比較を作る:割合と実量を100個の電球で読み解く

電源構成比較GridLightsの開発記録。12か国の年次発電データを最大剰余法で100個の電球に変換し、割合とTWhを比較する設計を解説。欠損を0補完しない7分類、予想と実値、出典付きSVGと実データ検証を整理します。

read log →
SoundLoom

音のお絵かきを作る:実WAVから絵を測り直す設計

音のお絵かきの開発記録。描画を正弦波のPCMとWAVへ変換し、保存音声の周波数をGoertzel法で測って比較する仕組みを解説。音量・測定窓・量子化の限界と、編集・離脱時の古い結果を抑止する検証を整理します。

read log →
MazeLoom

宝探し迷路を作る:寄り道まで含めた最短ルートの探索

宝探し迷路の開発記録。通路を木として生成し、現在地と3つの宝の取得状態を組み合わせて最短ルートを探す仕組みを解説。問題コードの版固定、画面と解答付きPDFの一致、手数と印刷の検証を整理します。

read log →