はじめに
サポート担当者は、各チケットのタイムラインと、イベントがないチケットも含めた集計結果を必要としています。この実験では、アクティビティの行をチケットに関連付け、再利用可能な SQL レポートを作成し、クエリプランで裏付けたインデックスを追加します。小さなデータセットで応答が速く見えるだけでは、効率的なアクセスパスの十分な証拠にはなりません。
この独立した実験では、以前の学習で使用した基本的なチケットスキーマを利用します。1 つの使い捨て D1 データベースを使い、リレーション、レポート、インデックスを自分で作成します。
自分の学習アカウントと新しい VM を使用してください。セットアップでは、まず Node.js 22.22.0 を準備し、その後 /home/labex/project/ticket-database でプロジェクトローカルの Wrangler 4.131.1 と評価用の依存関係を npm install でインストールします。直接依存関係のバージョンは固定されており、インストール時に専用の lockfile が作成されます。セットアップ中にクラウドへのログインや、評価対象となるデータベース操作は実行されません。個人のマシンでは、プロジェクト内で npm install --save-dev wrangler@4.131.1 を実行し、同じ Wrangler バージョンをインストールしてください。
この演習では、D1 Free allowances の範囲内にある小規模な合成レコードを使用します。既存のアカウント利用量もこの上限に含まれます。購入済みのドメインは必要ありません。リソースの削除とログアウトの両方を確認するまで、この VM を保持してください。
この VM を認証してアカウントを選択する
このステップでは、新しいターミナルを自分の学習アカウントに接続します。Dashboard にログインしただけでは VM は認証されません。D1 の権限により、データベースの作成、SQL の変更、削除を実行できます。認証する前に、Background Access を含む実際の同意画面を確認してください。
準備済みのプロジェクトに移動し、固定された CLI のバージョンを確認します。
cd /home/labex/project/ticket-database
npx wrangler --version
4.131.1 と表示されることを確認します。デバイス認証を開始します。--device はブラウザーで入力するコードを表示し、--browser=false は使用するブラウザーを自分で選べるようにします。
npx wrangler login --device --browser=false --scopes account:read user:read d1:write
表示された URL をブラウザーで開き、現在のコードを入力して、学習アカウントと権限を確認し、認証を許可します。ターミナルに成功の確認が表示されるまで待ちます。パスワードやトークンをプロジェクトファイルに貼り付けないでください。
npx wrangler whoami --json
loggedIn: true を確認し、アカウントが 1 つしか表示されない場合でも、アカウントの name と id を読み取ります。使用するアカウントの ID を、以下の設定にコピーします。次のシェル変数では、6 バイトのランダム値(12 個の 16 進文字)を使って、他の学習者との衝突を避けています。ヒアドキュメントにより、JSON の行の間にある JSON が書き込まれます。内部では $RUN が展開されます。
$schema の前のバックスラッシュは、この JSON キーを文字どおり保持します。$RUN は引き続き今回の実行に固有の名前に展開されます。
RUN=labex-c04-d04-$(openssl rand -hex 6)
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN",
"account_id": "YOUR_ACCOUNT_ID",
"main": "src/index.js",
"compatibility_date": "2026-09-15",
"workers_dev": true,
"preview_urls": false
}
JSON
ブロックを実行する前に YOUR_ACCOUNT_ID を置き換えてください。RUN を引き続き使用できるよう、このターミナルは開いたままにします。name は今回の実行を識別し、account_id はクラウド操作の対象アカウントを選択します。このファイルは通常の JSON であり、JSONC としても有効です。このファイルを書き込んだだけでは Worker はデプロイされません。
アクティビティをチケットに関連付ける
このステップでは、1 つのチケットに複数のイベントが発生したことを表すアクティビティテーブルを追加します。外部キーは、アクティビティの ticket_id を実在するチケットに関連付け、存在しない親を指すイベントを防ぎます。セットアップで使い慣れたチケットスキーマが提供されるため、ここではリレーションとクエリに集中できます。
使い捨てのクラウドデータベースを作成します。--binding DB はアプリケーションコードで使用する短い名前を設定し、--update-config は実際の名前と UUID を wrangler.jsonc に記録し、--use-remote=false は開発時にローカルデータベースを使用します。
npx wrangler d1 create "$RUN-db" --binding DB --update-config --use-remote=false
作成された名前と ID を読み取り、保存されたバインディングを確認します。
cat wrangler.jsonc
DB エントリが今回の実行用データベースを示している必要があります。バインディングは、コードとリソースの間に設定された接続です。その UUID はクラウドデータベースを識別し、--local を指定するとこの VM 内の別の SQLite データベースが使用されます。SQL コマンドでは、必ず --local または --remote のどちらかを指定してください。
提供されているチケットをローカルデータベースに読み込み、適用します。
cat schema.sql
npx wrangler d1 execute DB --local --file schema.sql
アクティビティのスキーマと固定データセットを作成します。ここでの整数タイムスタンプは、現在時刻ではなく、順序を表す合成値です。
cat > activity.sql <<'SQL'
CREATE TABLE activity (
id INTEGER PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES tickets(id),
action TEXT NOT NULL,
created_at INTEGER NOT NULL
);
INSERT INTO activity (id, ticket_id, action, created_at) VALUES
(1, 1, 'opened', 100),
(2, 1, 'assigned', 200),
(3, 2, 'opened', 110),
(4, 2, 'closed', 300);
INSERT INTO tickets (id, subject, source) VALUES (3, 'No activity yet', 'seed');
SQL
ローカルデータベースに適用します。
npx wrangler d1 execute DB --local --file activity.sql
チケット 1 と 2 にはそれぞれ 2 件のイベントがあり、チケット 3 にはイベントがありません。次のステップでは、イベントが 0 件のチケットを集計結果に含める方法を確認します。
チケットのアクティビティレポートを作成する
このステップでは、2 つのテーブルの行を組み合わせます。結合(join)は、リレーションを使って行を対応付けます。t と a はテーブル名の短い別名です。LEFT JOIN は、アクティビティがない場合でもすべてのチケットを残します。COUNT(a.id) は、一致したアクティビティ ID だけを数えます。GROUP BY はチケットごとにイベントをまとめ、AS event_count は計算した列に名前を付けます。
再利用可能な読み取り専用レポートクエリを作成します。クエリを SQL ファイルに保存すると、同じレポートをローカルとリモートの両方で実行できます。
cat > report.sql <<'SQL'
SELECT t.id, t.subject, COUNT(a.id) AS event_count
FROM tickets AS t
LEFT JOIN activity AS a ON a.ticket_id = t.id
GROUP BY t.id, t.subject
ORDER BY t.id;
SQL
レポートを実行します。
npx wrangler d1 execute DB --local --file report.sql
チケット 1、2、3 の件数として、それぞれ 2、2、0 が表示されることを確認します。内部結合を使うとチケット 3 が結果から除外されます。また、* を数えると、一致しないチケットに対応するプレースホルダー行まで数えてしまいます。1 つのチケットに属するイベントを時系列で読み取ります。
npx wrangler d1 execute DB --local --command "SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
100 の opened、続いて 200 の assigned が表示されることを確認します。レポートは各チケットのイベント数を答え、条件を指定したクエリは 1 つのチケットに属するイベントを答えます。
クエリプランを使ってインデックスの必要性を説明する
このステップでは、アクティビティのタイムラインを検索するための構造を追加します。インデックスは、検索可能な値を順序付けて保持し、関係のない行のスキャンを避けられるようにします。インデックスはストレージを使用し、インデックス対象の値が変更されるたびに追加の処理が発生するため、具体的なクエリに基づいて追加してください。
インデックスを追加する前にプランを確認します。EXPLAIN QUERY PLAN は SQLite のアクセス戦略を説明しますが、経過時間をベンチマークするものではありません。
npx wrangler d1 execute DB --local --command "EXPLAIN QUERY PLAN SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
activity のスキャンと、並べ替え用の一時構造が表示される可能性があります。ticket_id を先に、created_at を次に配置した複合インデックスを作成します。
cat > index.sql <<'SQL'
CREATE INDEX idx_activity_ticket_created ON activity(ticket_id, created_at);
SQL
npx wrangler d1 execute DB --local --file index.sql
npx wrangler d1 execute DB --local --command "EXPLAIN QUERY PLAN SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
プランに検索用の idx_activity_ticket_created が含まれることを確認します。正確な表示形式は異なる場合があります。固定データセットは小さすぎるため、実用的な処理時間の比較にはなりません。ここでの証拠は、選択されたアクセスパスです。
インデックスを作成した後に新しいイベントを追加し、レポートをもう一度実行します。
npx wrangler d1 execute DB --local --command "INSERT INTO activity (id, ticket_id, action, created_at) VALUES (5, 1, 'replied', 400);"
npx wrangler d1 execute DB --local --file report.sql
チケット 1 のイベント数が 3 件になったことを確認します。インデックスは読み取りをサポートするだけでなく、正しい書き込みも維持する必要があります。
D1 でレポートとインデックス付きクエリを実行する
このステップでは、確認済みのスキーマとインデックスをリモートの実験用データベースに適用します。ローカルファイルだけでは、リモートデータベースのスキーマは作成されません。
npx wrangler d1 execute DB --remote --file schema.sql
npx wrangler d1 execute DB --remote --file activity.sql
npx wrangler d1 execute DB --remote --file index.sql
確認を求められたら、今回の実験用データベースだけを対象にしていることを確認します。同じ最後のアクティビティを追加し、レポートとリモートのクエリプランを確認します。
npx wrangler d1 execute DB --remote --command "INSERT INTO activity (id, ticket_id, action, created_at) VALUES (5, 1, 'replied', 400);"
npx wrangler d1 execute DB --remote --command "$(cat report.sql)"
npx wrangler d1 execute DB --remote --command "EXPLAIN QUERY PLAN SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
$(cat report.sql) はレポートの内容をクエリとして渡します。リモートで --file を使うとインポートワークフローが実行され、SELECT 結果のテーブルではなくインポートのメタデータが表示されます。リモートの件数は 3、2、0 となり、プランには作成したインデックスが表示されます。これにより、特定の処理時間の改善を仮定せずに、D1 上で結果とアクセスパスを確認できます。必要であれば、この実行用の D1 データベースを Dashboard で開き、読み取り専用のテーブル/スキーマ確認を行ってください。インデックスの証拠として、CLI のプランは保持します。

この例では、リモートの 5 行の活動データと ticket_id による関連を示しています。生成されたデータベース名はこの実行例を識別するもので、あなたの名前は異なります。created_at の数値は並べ替え用の架空の値であり、現在のタイムスタンプではありません。この表は行を確認するための参考です。上記の CLI の EXPLAIN QUERY PLAN 出力はインデックスの使用を確認するもので、処理時間の改善を証明するものではありません。
使い捨てリソースを削除する
このステップでは、VM がまだ認証されている間に、この実験のリソースだけを削除します。まず、すべての機能確認を完了してください。削除の検証が終わるまで設定を保持します。
npx wrangler d1 delete DB
プロンプトを確認し、今回の実行用データベースだけを削除することを確認します。その後、データベースを一覧表示します。
npx wrangler d1 list --json
成功したレスポンスに、記録しておいたデータベース名と UUID が含まれていないことを確認します。他のリソースは残っている可能性があります。認証エラーやネットワークエラーが発生した場合、その結果だけでは削除を判断できません。アクセスを解決してから、続行する前に一覧表示をもう一度実行してください。このステップの検証は、ログインしたまま行います。
この VM の認証を終了する
このステップでは、独立した削除確認に成功した後でのみ認証を終了します。ログアウトすると、この VM に保存されている Wrangler の認証情報が削除されます。VM を閉じただけでは、クラウドのクリーンアップにはなりません。
npx wrangler logout
npx wrangler whoami --json
loggedIn: false と表示されることを確認します。この未認証のクエリは終了ステータスが 0 以外になる場合があります。構造化されたレスポンスでログアウト済みであることが明示されている場合に限り、それは想定された結果です。検証を完了してから、実験環境を終了します。
まとめ
チケットのアクティビティをクエリし、インデックスを作成する練習をしました。データベースの結果を確認し、選択したアカウントとローカル状態を明示的に管理し、ログアウトする前に使い捨てリソースを削除しました。



