database-fundamentals
Auto-invoke when reviewing schema design, database queries, ORM usage, or migrations. Enforces normalization, indexing awareness, query optimization, and migration safety.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o database-fundamentals.zip https://jpskill.com/download/18278.zip && unzip -o database-fundamentals.zip && rm database-fundamentals.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/18278.zip -OutFile "$d\database-fundamentals.zip"; Expand-Archive "$d\database-fundamentals.zip" -DestinationPath $d -Force; ri "$d\database-fundamentals.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
database-fundamentals.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
database-fundamentalsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
データベース基礎レビュー
「データベースは基礎です。それを間違って構築すると、その上のすべてにひびが入ります。」
適用するタイミング
以下をレビューする際にこのスキルを有効にします。
- スキーマ設計とマイグレーション
- SQL/NoSQL クエリ
- ORM モデル定義
- データリレーションシップ
- インデックス作成
- クエリパフォーマンス
レビューチェックリスト
スキーマ設計
- [ ] 正規化: データは適切に正規化されていますか(過剰な重複がないか)?
- [ ] 非正規化の正当性: 非正規化されている場合、パフォーマンス上の理由がありますか?
- [ ] 主キー: すべてのテーブルに明確な主キーがありますか?
- [ ] 外部キー: リレーションシップはデータベースレベルで強制されていますか?
- [ ] データ型: 適切な型が使用されていますか(すべてが TEXT ではないか)?
インデックス
- [ ] クエリベース: 頻繁にクエリされるカラムに対してインデックスが作成されていますか?
- [ ] 複合インデックス: 複数カラムのクエリはカバーされていますか?
- [ ] 過剰なインデックスではない: 不要なインデックスが書き込みを遅くしていませんか?
- [ ] 一意制約: 一意のフィールドは DB レベルで強制されていますか?
クエリ
- [ ] N+1 問題なし: 関連レコードはまとめてフェッチされていますか?
- [ ] 特定のフィールドを選択:
SELECT *を避けていますか? - [ ] ページネーション: リストクエリは結果を制限していますか?
- [ ] パラメータ化: すべてのクエリはパラメータ化されていますか(文字列連結がないか)?
マイグレーション
- [ ] 可逆性: このマイグレーションはロールバックできますか?
- [ ] データ損失なし: 既存のデータはマイグレーション後も残りますか?
- [ ] テスト済み: これは本番環境のようなデータに対してテストされていますか?
- [ ] 段階的: 大きな変更はより小さなマイグレーションに分割されていますか?
よくある間違い(アンチパターン)
1. N+1 クエリ問題
❌ // ユーザに対する 1 つのクエリ + 投稿に対する N 個のクエリ
const users = await User.findAll();
for (const user of users) {
user.posts = await Post.findAll({ where: { userId: user.id } });
}
✅ // JOIN を使用した 1 つのクエリ
const users = await User.findAll({
include: [{ model: Post }]
});
// または IN 句を使用した 2 つのクエリ
const users = await User.findAll();
const userIds = users.map(u => u.id);
const posts = await Post.findAll({ where: { userId: userIds } });
2. インデックスの欠落
❌ // 頻繁にクエリされるが、インデックスがない
SELECT * FROM orders WHERE user_id = ?
SELECT * FROM products WHERE category = ? AND status = 'active'
✅ CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_products_category_status ON products(category, status);
3. あらゆる場所での SELECT *
❌ SELECT * FROM users; // 50 個のカラムを返す
✅ SELECT id, name, email FROM users; // 必要なものだけ
4. 文字列連結 (SQL インジェクション)
❌ db.query(`SELECT * FROM users WHERE email = '${email}'`);
✅ db.query('SELECT * FROM users WHERE email = ?', [email]);
5. 破壊的なマイグレーション
❌ -- ロールバックできない
DROP TABLE users;
ALTER TABLE orders DROP COLUMN status;
✅ -- 新しいものを追加し、データを移行してから、古いものを削除する(別々のマイグレーションで)
-- マイグレーション 1: 新しいカラムを追加する
ALTER TABLE orders ADD COLUMN status_new VARCHAR(20);
-- マイグレーション 2: データをコピーする
UPDATE orders SET status_new = status;
-- マイグレーション 3: 古いものを削除する(検証後)
ALTER TABLE orders DROP COLUMN status;
ソクラテス式質問
ジュニアに答えを与える代わりに、これらの質問をしてください。
- スキーマ: 「なぜこのデータ型を選んだのですか?」
- リレーションシップ: 「この関連レコードが削除された場合、どうなりますか?」
- インデックス: 「どのカラムが一緒にクエリされますか?それらはインデックスされていますか?」
- N+1: 「この操作はいくつのクエリを実行しますか?」
- マイグレーション: 「これをロールバックする必要がある場合、どうなりますか?」
正規化クイックリファレンス
| 形式 | ルール | 例となる問題 |
|---|---|---|
| 1NF | 繰り返しグループがない | tags: "js,react,node" は別のテーブルにする必要がある |
| 2NF | 部分的な依存関係がない | 注文商品の価格が商品から重複している |
| 3NF | 推移的な依存関係がない | 市と郵便番号を保存する(郵便番号が市を決定する) |
非正規化するタイミング
- 書き込みがまれな読み込みヘビーなワークロード
- 計算された集計(例:注文合計)
- 頻繁にアクセスされる派生データのキャッシュ
インデックス戦略
-- 単一カラム (最も一般的)
CREATE INDEX idx_users_email ON users(email);
-- 複合 (複数カラムのクエリ用)
-- 順序が重要!最も選択的なものを最初に
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- 部分 (フィルタリングされたクエリ用)
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- 一意 (制約を強制)
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
インデックスの経験則
- WHERE 句のカラムをインデックス化する
- JOIN 条件のカラムをインデックス化する
- ORDER BY 句のカラムをインデックス化する (WHERE と一緒に使用する場合)
- 書き込みヘビーなテーブルを過剰にインデックス化しない
- 複数カラムのクエリには複合インデックスを検討する
クエリ最適化チェックリスト
- [ ] EXPLAIN を使用してクエリプランを分析する
- [ ] SELECT * を避ける - カラムを指定する
- [ ] ページネーションには LIMIT を使用する
- [ ] WHERE/JOIN カラムにインデックスを追加する
- [ ] 可能な場合は HAVING の代わりに WHERE を使用する
- [ ] WHERE 句でインデックスされたカラムに関数を使用しない
- [ ] 大きなサブクエリには IN の代わりに EXISTS を使用する
指摘すべき危険信号
| フラグ | 質問 |
|---|---|
| ループ内のクエリ | 「このデータをすべて 1 つのクエリでフェッチできますか?」 |
| ページネーションなし | 「100 万件のレコードがある場合はどうなりますか?」 |
| SELECT * | 「50 個のカラムすべてが必要ですか?」 |
| クエリ内の文字列 | 「これは SQL インジェクションから保護されていますか?」 |
| 外部キーにインデックスがない | 「このテーブルでの JOIN はどのくらい高速ですか?」 |
| マイグレーションでの DROP TABLE | 「これをどのようにロールバックしますか?」 |
| すべてに TEXT | 「これは代わりに INT または DATE にすべきではありませんか?」 |
| 外部キー制約がない | 「孤立したレコードを防ぐものは何ですか?」 |
ORM ベストプラクティス
// Eager loading (N+1 を避ける)
const users = await User.findAll({
include: [{ model: Post, attributes: ['id', 'title'] }]
});
// 特定のフィールドを選択
const users = await User.findAll({
attributes: ['id', 'nam 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Database Fundamentals Review
"Your database is the foundation. Build it wrong, and everything above it will crack."
When to Apply
Activate this skill when reviewing:
- Schema design and migrations
- SQL/NoSQL queries
- ORM model definitions
- Data relationships
- Index creation
- Query performance
Review Checklist
Schema Design
- [ ] Normalization: Is data normalized appropriately (no excessive duplication)?
- [ ] Denormalization justified: If denormalized, is there a performance reason?
- [ ] Primary keys: Does every table have a clear primary key?
- [ ] Foreign keys: Are relationships enforced at the database level?
- [ ] Data types: Are appropriate types used (not everything TEXT)?
Indexes
- [ ] Query-based: Are indexes created for frequently queried columns?
- [ ] Composite indexes: Are multi-column queries covered?
- [ ] Not over-indexed: Are there unnecessary indexes slowing writes?
- [ ] Unique constraints: Are unique fields enforced at DB level?
Queries
- [ ] No N+1: Are related records fetched in bulk?
- [ ] Select specific fields: Are we avoiding
SELECT *? - [ ] Pagination: Do list queries limit results?
- [ ] Parameterized: Are all queries parameterized (no string concatenation)?
Migrations
- [ ] Reversible: Can this migration be rolled back?
- [ ] No data loss: Will existing data survive the migration?
- [ ] Tested: Has this been tested against production-like data?
- [ ] Incremental: Are large changes broken into smaller migrations?
Common Mistakes (Anti-Patterns)
1. The N+1 Query Problem
❌ // 1 query for users + N queries for posts
const users = await User.findAll();
for (const user of users) {
user.posts = await Post.findAll({ where: { userId: user.id } });
}
✅ // 1 query with JOIN
const users = await User.findAll({
include: [{ model: Post }]
});
// Or 2 queries with IN clause
const users = await User.findAll();
const userIds = users.map(u => u.id);
const posts = await Post.findAll({ where: { userId: userIds } });
2. Missing Indexes
❌ // Queried frequently, but no index
SELECT * FROM orders WHERE user_id = ?
SELECT * FROM products WHERE category = ? AND status = 'active'
✅ CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_products_category_status ON products(category, status);
3. SELECT * Everywhere
❌ SELECT * FROM users; // Returns 50 columns
✅ SELECT id, name, email FROM users; // Only what's needed
4. String Concatenation (SQL Injection)
❌ db.query(`SELECT * FROM users WHERE email = '${email}'`);
✅ db.query('SELECT * FROM users WHERE email = ?', [email]);
5. Destructive Migrations
❌ -- Can't be rolled back
DROP TABLE users;
ALTER TABLE orders DROP COLUMN status;
✅ -- Add new, migrate data, then drop old (in separate migrations)
-- Migration 1: Add new column
ALTER TABLE orders ADD COLUMN status_new VARCHAR(20);
-- Migration 2: Copy data
UPDATE orders SET status_new = status;
-- Migration 3: Drop old (after verification)
ALTER TABLE orders DROP COLUMN status;
Socratic Questions
Ask the junior these questions instead of giving answers:
- Schema: "Why did you choose this data type?"
- Relationships: "What happens if this related record is deleted?"
- Indexes: "Which columns are queried together? Are they indexed?"
- N+1: "How many queries does this operation execute?"
- Migration: "What happens if we need to roll this back?"
Normalization Quick Reference
| Form | Rule | Example Issue |
|---|---|---|
| 1NF | No repeating groups | tags: "js,react,node" should be separate table |
| 2NF | No partial dependencies | Order item price duplicated from products |
| 3NF | No transitive dependencies | Storing city AND zip code (zip determines city) |
When to Denormalize
- Read-heavy workloads with rare writes
- Calculated aggregates (e.g., order totals)
- Caching frequently accessed derived data
Index Strategy
-- Single column (most common)
CREATE INDEX idx_users_email ON users(email);
-- Composite (for multi-column queries)
-- Order matters! Most selective first
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- Partial (for filtered queries)
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- Unique (enforces constraint)
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
Index Rules of Thumb
- Index columns in WHERE clauses
- Index columns in JOIN conditions
- Index columns in ORDER BY (if used with WHERE)
- Don't over-index write-heavy tables
- Consider composite indexes for multi-column queries
Query Optimization Checklist
- [ ] Use EXPLAIN to analyze query plan
- [ ] Avoid SELECT * - specify columns
- [ ] Use LIMIT for pagination
- [ ] Add indexes for WHERE/JOIN columns
- [ ] Use WHERE instead of HAVING when possible
- [ ] Avoid functions on indexed columns in WHERE
- [ ] Use EXISTS instead of IN for large subqueries
Red Flags to Call Out
| Flag | Question to Ask |
|---|---|
| Query in a loop | "Can we fetch all this data in one query?" |
| No pagination | "What if there are 1 million records?" |
| SELECT * | "Do we need all 50 columns?" |
| String in query | "Is this protected against SQL injection?" |
| No indexes on foreign keys | "How fast are JOINs on this table?" |
| DROP TABLE in migration | "How do we roll this back?" |
| TEXT for everything | "Should this be an INT or DATE instead?" |
| No foreign key constraints | "What prevents orphaned records?" |
ORM Best Practices
// Eager loading (avoid N+1)
const users = await User.findAll({
include: [{ model: Post, attributes: ['id', 'title'] }]
});
// Select specific fields
const users = await User.findAll({
attributes: ['id', 'name', 'email']
});
// Pagination
const users = await User.findAll({
limit: 20,
offset: (page - 1) * 20
});
// Raw queries for complex operations
const results = await sequelize.query(
'SELECT ... complex query ...',
{ type: QueryTypes.SELECT }
);