はじめに
この記事では、テストデータを効率良く作る方法として、SQL で管理するやり方を整理します。
結論を先に書くと、手動検証やバッチ検証では、テストデータ作成を SQL に寄せた方が扱いやすいことが多いです。
特に次のような場面を対象にしています。
- バッチの手動検証をしたい
- 既存データを消して毎回同じ状態を作りたい
- 複数テーブルにまたがる前提データをまとめて用意したい
- 新規テーブルの検証なので、テーブル作成から入れたい
テストデータ作成の方法はいくつかあります。
- 管理画面から手で登録する
- アプリの Seeder を使う
- テストコード内でデータを作る
- SQL を直接流す
どれも使いどころはあります。
ただし、用途によって向き不向きはあります。
大量データの編集や表形式での管理なら、CSV や TSV の方が扱いやすい場面もあります。
一方で、手動検証や DB 状態確認を伴う実務では、SQL がかなり強い選択肢になります。
先に結論
効率良くテストデータを作りたいなら、次の形で SQL を管理するのがおすすめです。
- 変数や対象期間を最初に定義する
- 既存データを削除する
- 必要ならテーブル作成も入れる
- マスタや前提データを投入する
- テストケースごとのデータを投入する
- 最後に確認用の
SELECTを入れる
つまり、単なる INSERT 集ではなく、「どの環境でも流し込める再現用スクリプト」として SQL を作るのが重要です。
先に使い分けを書く
先に書いておくと、常に SQL が最適だと言いたいわけではありません。
テストデータ管理には、CSV や TSV が向いている場面もあります。
例えば次のような点では、CSV や TSV はかなり強いです。
- 大量データをまとめて扱いやすい
- 表形式で編集しやすい
- 列追加や列削除に追従しやすい
- 非エンジニアでも触りやすい
特に「データそのものを管理する」「件数の多い初期データを流し込む」という観点では、CSV や TSV の方が自然です。
一方で、この記事で扱いたいのは、状態を再現するためのテストデータです。
この用途では、次のように考えると整理しやすいです。
- SQL は状態を作るスクリプト
- CSV や TSV は状態のスナップショット
この違いがあるため、SQL には CSV や TSV にはない強みがあります。
なぜ SQL が強いのか
自分は、少なくとも実務の手動検証では、テストデータ作成は SQL がかなり強いと考えています。
理由は次のとおりです。
- DB に最も近い形で書ける
- 実際に入るデータをそのまま確認できる
- 複数テーブルの整合を一気に作れる
- アプリ実装に依存しにくい
- どの環境でも流し込みやすい
ここでいう「どの環境でも流し込みやすい」というのは、プログラミング言語の実装に依存しにくいという意味です。
例えば次のような場面でも同じ SQL を使いやすいです。
- ローカル環境
- 検証環境
- ステージング環境
- 手元の DB クライアント
- CI で立ち上げた一時 DB
もちろん、DB 製品ごとの差分はあります。
ただ、それでもアプリの Seeder や専用スクリプトよりは「DB に直接流せる形」で共有しやすいことが多いです。
この性質は実務ではかなり重要です。
例えば、ローカルでは流せるが検証環境ではアプリ起動手順が違って使えない、という状況はよくあります。
SQL なら、接続先さえあれば同じ内容をそのまま流しやすいため、環境差分の影響を受けにくくなります。
例えば、Go の Seeder や Java の初期化コードでテストデータを作ると、次の問題が起きやすいです。
- アプリが起動しないと使えない
- ORM の書き方に引っ張られる
- DB の状態を直接見たいときに遠回りになる
- 他メンバーが SQL ではなくコードを読まないといけない
一方、SQL なら DB を見る人にとって自然です。
特に、バックエンド、QA、DB を見るメンバーが混ざる現場では強いです。
SQL は意図をデータと一緒に持てる
SQL の強みは、単にデータを入れられることではありません。
そのデータが何のためにあるのかを、一緒に残せることです。
例えば次のような形です。
-- ケース1: 本人確認済み + 高額入金ユーザー
INSERT INTO users (id, status, kyc_status)
VALUES (1, 'active', 'verified');
この形なら、データの意味がそのまま残ります。
一方、CSV はデータ自体は見やすいですが、その1行が何のケースなのかは別で管理しないと分かりにくくなります。
id,status,kyc_status
1,active,verified
テストデータでは「この行は何を検証するためのものか」が重要なので、この差は小さくありません。
SQL は前処理と後処理を一体で書ける
SQL は、投入だけでなく前処理と後処理も同じファイルにまとめられます。
例えば次のような形です。
DELETE FROM users;
-- ケース投入
INSERT INTO users (id, status, kyc_status)
VALUES (1, 'active', 'verified');
-- 検証
SELECT *
FROM users
WHERE id = 1;
この形にしておくと、削除、投入、確認までを1ファイルで完結できます。
これは単なるデータファイルではなく、状態再現用のスクリプトとして扱える、ということです。
SQL は依存関係や条件も表現しやすい
テストデータでは、値があるだけでは足りないことがあります。
例えば次のような条件です。
- 外部キー順序を守りたい
-
UPDATE前提の初期状態を作りたい - 既存データと突き合わせたい
- 条件ごとにケースを分けたい
こうした場面では、CSV や TSV だけだと表現が足りません。
SQL なら、削除順序、投入順序、条件付き更新、確認用 SELECT まで含めて書けます。
既存データ削除から入れた方がよい
ここはかなり重要です。
テストデータ SQL は、最初に既存データ削除から入れた方が再現性が上がります。
例えば、INSERT だけを並べた SQL は最初の1回は動いても、2回目以降につらくなります。
- 一意制約に引っかかる
- 前回データが残っていて件数が変わる
- update 検証なのか insert 検証なのか分からなくなる
そのため、まずは対象データを消してから作る方が安定します。
DELETE FROM order_summary
WHERE user_id IN ('TEST001', 'TEST002')
AND target_month = '2026-03-01';
DELETE FROM orders
WHERE user_id IN ('TEST001', 'TEST002', 'TEST003');
この形にしておくと、何回流しても同じ状態を作りやすくなります。
テストデータSQLは「毎回同じ状態を作れるか」が重要
実務で重要なのは、データを作れることではなく、同じ状態を何回でも再現できることです。
例えばバッチ検証では、次の流れを何度もやります。
- データを作る
- バッチを動かす
- 結果を見る
- 条件を変える
- もう一度試す
このとき、前回データが残っていると比較しにくくなります。
だからこそ、SQL は次の順にした方がよいです。
- 削除
- 投入
- 確認
単純ですが、この形が一番壊れにくいです。
変数定義を最初に置く
今回の SQL ファイルでも良かったのは、最初に変数や対象期間を定義している点です。
これはかなり実務的です。
SET @target_date = '2026-03-01';
SET @next_date = DATE_ADD(@target_date, INTERVAL 1 MONTH);
SET @test_user_1 = 'TEST001';
SET @test_user_2 = 'TEST002';
変数を使う利点は次のとおりです。
- 同じ ID や日付を何度も直さなくて済む
- ケースの意図が分かりやすい
- 月次バッチのような期間条件を書き換えやすい
特に日付系の検証では、変数化しておかないと修正漏れが起きやすいです。
ユーザー変数には文字コード指定も入れる
ユーザー変数(@ 変数)は、代入時の接続セッションの collation を保持します。
テーブル側の列と collation が一致していないと、次のエラーが出ることがあります。
Illegal mix of collations
例えば、リテラルなら通るのに @ 変数を使うと失敗するケースがあります。
詳しい仕組みは「MySQLでリテラルは通るのにユーザー変数でIllegal mix of collationsが出る理由」を参照してください。
対処は、変数定義の前に SET NAMES を1行入れるだけです。
-- 接続の文字コードと collation をテーブルに合わせる
SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;
SET @target_date = '2026-03-01';
SET @test_user_1 = 'TEST001';
テストデータ SQL は Workbench やターミナルなど、複数の接続ツールから流すことがあります。
接続ツールによって collation の初期値が変わることがあるため、先頭に入れておくと安全です。
ケースごとにコメントを書く
テストデータ SQL は、単なるデータ投入ではなく検証仕様の一部です。
そのため、ケースごとにコメントを書いた方が読みやすくなります。
例えば次のような形です。
INSERT INTO orders (order_id, user_id, order_date, status)
VALUES
-- ケースA: 当月注文。集計対象
('ORD001', @test_user_1, DATE_ADD(@target_date, INTERVAL 1 DAY), 'completed'),
-- ケースB: 翌月注文。集計対象外
('ORD002', @test_user_1, DATE_ADD(@next_date, INTERVAL 1 DAY), 'completed'),
-- ケースC: キャンセル済み。集計対象外
('ORD003', @test_user_2, DATE_ADD(@target_date, INTERVAL 2 DAY), 'canceled');
これをやっておくと、後から見ても「なぜこのデータがあるのか」が分かります。
新規テーブルならテーブル作成も同じSQLに入れてよい
新規に作成するテーブルの検証なら、テーブルがない場合は作成 SQL も同じファイルに入れてよいと思います。
むしろ、その方が分かりやすいことがあります。
例えば次のような形です。
CREATE TABLE IF NOT EXISTS order_summary (
user_id VARCHAR(20) NOT NULL,
target_month DATE NOT NULL,
order_count INT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (user_id, target_month)
);
この形の利点は次のとおりです。
- 新規参加者でもそのまま流せる
- 手元 DB にテーブルがない状態から始められる
- 検証用テーブルの前提が明確になる
ただし、既存本番テーブルの正式な DDL 管理とは分けるべきです。
ここで入れるのは、あくまで検証や再現に必要な最低限の DDL です。
DDL と DML を同じファイルに入れるときの考え方
新規テーブル検証では、次のようにまとめると扱いやすいです。
CREATE TABLE IF NOT EXISTSDELETEINSERTSELECT
例えば次の流れです。
CREATE TABLE IF NOT EXISTS test_orders (
id BIGINT PRIMARY KEY,
user_id VARCHAR(20) NOT NULL,
order_date DATE NOT NULL
);
DELETE FROM test_orders WHERE user_id IN ('TEST001', 'TEST002');
INSERT INTO test_orders (id, user_id, order_date)
VALUES
(1, 'TEST001', '2026-03-01'),
(2, 'TEST002', '2026-03-02');
SELECT * FROM test_orders WHERE user_id IN ('TEST001', 'TEST002');
このくらい素直な構成の方が、実務では読みやすいです。
SQLだけでなく最後に確認用SELECTも入れる
投入して終わりではなく、最後に確認用 SELECT を入れるのも大事です。
これは地味ですが、かなり効きます。
SELECT *
FROM order_summary
WHERE user_id IN ('TEST001', 'TEST002')
AND target_month = '2026-03-01';
これがあると、次の点が楽になります。
- 投入結果をすぐ確認できる
- バッチ実行前の状態を見られる
- 他の人が流しても期待状態を把握しやすい
管理画面や Seeder より SQL が向いている場面
もちろん、管理画面や Seeder、CSV や TSV が不要という話ではありません。
ただし、次の場面では SQL の方がかなり強いです。
- 月次バッチや集計バッチの検証
- 境界値を含むデータ作成
- 複数テーブルの整合を明示したい場合
- update 前提の初期値をわざと入れたい場合
- DB を直接見ながら確認したい場合
例えば、集計結果が update されることを確認したいなら、先にわざと不正な値を入れておく、というやり方ができます。
INSERT INTO order_summary (user_id, target_month, order_count, created_at, updated_at)
VALUES ('TEST001', '2026-03-01', 99, NOW(), NOW());
このような「実行前にあえて 99 を入れておく」検証は、SQL の方が意図を表現しやすいです。
SQLで管理するときの注意点
便利ですが、雑に書くと逆に読みづらくなります。
注意点は次のとおりです。
- 何を検証するデータかコメントを書く
- 削除対象を広げすぎない
- 変数で意味を持たせる
- 本番用 DDL 管理と検証用 SQL を混ぜすぎない
- 最後に確認用
SELECTを付ける
特に DELETE は危険なので、対象を絞った方がよいです。
おすすめのテンプレート
実務では、次のテンプレートから始めると作りやすいです。
-- 0. 文字コード指定
SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;
-- 1. 変数定義
SET @target_date = '2026-03-01';
SET @test_id_1 = 'TEST001';
-- 2. 必要ならDDL
CREATE TABLE IF NOT EXISTS sample_table (
id BIGINT PRIMARY KEY,
user_id VARCHAR(20) NOT NULL
);
-- 3. 事前クリーンアップ
DELETE FROM sample_result WHERE user_id = @test_id_1;
DELETE FROM sample_table WHERE user_id = @test_id_1;
-- 4. 前提データ投入
INSERT INTO sample_result (user_id, count_value)
VALUES (@test_id_1, 99);
-- 5. テストデータ投入
INSERT INTO sample_table (id, user_id)
VALUES (1, @test_id_1);
-- 6. 確認
SELECT * FROM sample_table WHERE user_id = @test_id_1;
SELECT * FROM sample_result WHERE user_id = @test_id_1;
この並びにしておくと、読み手も流れを追いやすいです。
まとめ
効率良くテストデータを作りたいなら、用途ごとに管理方法を分けて考えるのが大事です。
特に次の整理が重要です。
- CSV や TSV はデータ管理や編集に強い
- SQL は意図、手順、再現性に強い
そのうえで、手動検証やバッチ検証では SQL がかなり強いです。
特に重要なのは次の点です。
-
INSERTだけでなく、既存データ削除から入れる - 変数を最初に定義する
- ケースごとにコメントを書く
- 必要ならテーブル作成も同じ SQL に入れる
- 最後に確認用
SELECTを付ける - ローカル、検証環境、ステージングでも同じ SQL を流しやすい形にする
テストデータ作成は、単にデータを入れる作業ではありません。
毎回同じ状態を再現できることが大事です。
その意味で、SQL を「再現用スクリプト」として管理すると、手動検証もチーム共有もしやすくなります。
特にテストデータでは、「なぜこのデータが必要か」をコードとして残せる点に価値があります。