メインコンテンツへスキップ
SDETRole
SDETRole

学ぶ / チートシート

テスターのための SQL チートシート

UI や API の裏側にあるデータを検証するためにテスターが使う SQL です。レコードを見つけ、変更されたことを確認し、あってはならない重複、孤立レコード、NULL を探します。構文は PostgreSQL、MySQL 8、SQLite で動く標準 SQL です。データベースによって異なる点は注記に記載しています。

標準 SQL。PostgreSQL 16、MySQL 8、SQLite 3 で確認済み で確認済み

行を検索する

やりたいことコード
テーブルから列を取得 SELECT id, email FROM users;
絞り込み SELECT * FROM orders WHERE status = 'paid' AND total > 100;
複数の値のいずれか WHERE status IN ('paid', 'shipped')
範囲(両端を含む) WHERE created_at BETWEEN '2026-01-01' AND '2026-01-31'
パターン WHERE email LIKE '%@example.com'
値がない WHERE phone IS NULL
新しい順に並べる ORDER BY created_at DESC
先頭 10 行 LIMIT 10
重複のない値 SELECT DISTINCT status FROM orders;

SQL Server では LIMIT の代わりに SELECT TOP 10 を使います。タイムスタンプに対する BETWEEN は終了日の 0 時ちょうどまでしか含みません。日単位で丸ごと含めたいなら「< 翌日」を使います。

集計

やりたいことコード
行数を数える SELECT COUNT(*) FROM orders;
NULL 以外の値を数える SELECT COUNT(shipped_at) FROM orders;
重複を除いて数える SELECT COUNT(DISTINCT user_id) FROM orders;
グループごとの合計 SELECT status, COUNT(*), SUM(total) FROM orders GROUP BY status;
グループを絞り込む SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 5;
最小・最大・平均 SELECT MIN(total), MAX(total), AVG(total) FROM orders;

WHERE はグループ化の前に行を絞り込み、HAVING はグループ化の後にグループを絞り込みます。

結合

やりたいことコード
一致する行のみ SELECT o.id, u.email FROM orders o JOIN users u ON u.id = o.user_id;
左側の行をすべて残す SELECT u.email, o.id FROM users u LEFT JOIN orders o ON o.user_id = u.id;
一致しない行(アンチ結合) SELECT u.* FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE o.id IS NULL;
同じことを NOT EXISTS で SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

1 対多のキーで結合すると「1」側の行が繰り返されます。結合後に SUM() が大きすぎるように見えたら、結合で行が増殖(ファンアウト)しています。

テスターがよく実行するデータチェック

やりたいことコード
重複 SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
孤立レコード(親のない子) SELECT o.* FROM orders o LEFT JOIN users u ON u.id = o.user_id WHERE u.id IS NULL;
必須項目が空 SELECT * FROM users WHERE email IS NULL OR TRIM(email) = '';
範囲外の値 SELECT * FROM order_items WHERE quantity <= 0 OR unit_price < 0;
ヘッダーの合計 ≠ 明細行の合計 SELECT o.id FROM orders o JOIN order_items i ON i.order_id = o.id GROUP BY o.id, o.total HAVING o.total <> SUM(i.quantity * i.unit_price);
UI 操作後に行が存在するか SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC LIMIT 1;

NULL の落とし穴

落とし穴何が起きるか
WHERE col = NULL 決して真になりません。IS NULL / IS NOT NULL を使います。
WHERE status <> 'paid' status が NULL の行は除外されます。含めたい場合は OR status IS NULL を追加します。
NOT IN(NULL を返すサブクエリ) 1 行も返りません。代わりに NOT EXISTS を使います。
COUNT(col) と COUNT(*) COUNT(col) は NULL を数えず、COUNT(*) はすべての行を数えます。
NULL を含む算術演算 price + NULL は NULL になります。0 が妥当なデフォルトなら COALESCE(col, 0) で囲みます。

ウィンドウ関数

やりたいことコード
グループごとに行番号を振る ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)
グループごとの最新行 SELECT * FROM (SELECT o.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders o) t WHERE rn = 1;
同順位ありの順位付け RANK() OVER (ORDER BY total DESC) / DENSE_RANK() …
前の行の値 LAG(status) OVER (PARTITION BY test_name ORDER BY run_at)
累計 SUM(total) OVER (ORDER BY created_at)

PostgreSQL、MySQL 8 以降、SQLite 3.25 以降、SQL Server でサポートされています。ウィンドウ関数は WHERE では使えないので、外側のクエリで絞り込みます。

データを安全に変更する

やりたいことコード
挿入 INSERT INTO users (email, name) VALUES ('qa+1@example.com', 'QA One');
更新(必ず WHERE を付ける) UPDATE orders SET status = 'cancelled' WHERE id = 1043;
削除(必ず WHERE を付ける) DELETE FROM sessions WHERE user_id = 42;
試してから元に戻す
BEGIN;
UPDATE orders SET status = 'cancelled' WHERE id = 1043;
SELECT status FROM orders WHERE id = 1043;
ROLLBACK;

UPDATE や DELETE の前に、同じ WHERE で SELECT を実行し、行数を確認しましょう。

練習してさらに学ぶ

その他のチートシート

キーワードを入力すると SDETRole 全体を検索できます。

Pagefind による全文検索 · ブラウザ内で動作します 検索ページを開く →