sql-database-assistant
alirezarezvani/claude-skills
Traduisez le langage naturel en requêtes SQL, optimisez les performances des bases de données, générez des migrations, explorez les schémas et utilisez des ORM avec PostgreSQL, MySQL, SQLite et SQL Server.
...Développer toutAssistant de base de données SQL - Compétence de niveau « POWERFUL »
Présentation
Le compagnon opérationnel de la conception de bases de données. Alors que le concepteur de bases de données se concentre sur l’architecture du schéma et que le concepteur de schémas de bases de données gère la modélisation ERD, cette compétence couvre les tâches quotidiennes : rédaction de requêtes, optimisation des performances, génération de migrations et mise en relation du code applicatif et des moteurs de bases de données.
Compétences clés
- Du langage naturel au SQL — traduire les exigences en requêtes correctes et performantes
- Exploration de schémas — analyse des bases de données en production sur PostgreSQL, MySQL, SQLite et SQL Server
- Optimisation des requêtes — analyse EXPLAIN, recommandations d’indexation, détection des problèmes N+1, modèles de réécriture
- Génération de migrations — scripts de mise à niveau et de retour en arrière, stratégies sans temps d’arrêt, plans de restauration
- Intégration ORM — Prisma, Drizzle, TypeORM, SQLAlchemy : modèles et solutions de secours
- Prise en charge multi-bases de données — SQL tenant compte des dialectes avec conseils de compatibilité
Outils
| Script | Objectif |
|---|---|
scripts/query_optimizer.py |
Analyse statique des requêtes SQL pour détecter les problèmes de performances |
scripts/migration_generator.py |
Génération de modèles de fichiers de migration à partir de descriptions de modifications |
scripts/schema_explorer.py |
Génération de la documentation du schéma à partir de requêtes d'introspection |
Du langage naturel vers le SQL
Modèles de traduction
Lors de la conversion des exigences en SQL, suivez cette séquence :
- Identifier les entités — associer les noms à des tables
- Identifier les relations — associer les verbes à des JOIN ou à des sous-requêtes
- Identifier les filtres — associer les adjectifs/conditions aux clauses WHERE
- Identifier les agrégations — associer « total », « moyenne », « nombre » à GROUP BY
- Identifier les critères de tri — associer « top », « latest », « highest » à ORDER BY + LIMIT
Modèles de requêtes courants
Top-N par groupe (fonction de fenêtre)
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees
) ranked WHERE rn <= 3;
Totaux cumulés
SELECT date, montant,
SUM(montant) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total_cumulé
FROM transactions;
Détection des écarts
SELECT curr.id, curr.seq_num, prev.seq_num AS prev_seq
FROM records curr
LEFT JOIN records prev ON prev.seq_num = curr.seq_num - 1
WHERE prev.id IS NULL AND curr.seq_num > 1;
UPSERT (PostgreSQL)
INSERT INTO settings (key, value, updated_at)
VALUES ('theme', 'dark', NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXCLUDED.updated_at;
UPSERT (MySQL)
INSERT INTO settings (key_name, value, updated_at)
VALUES ('theme', 'dark', NOW())
ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at);
Consultez le fichier references/query_patterns.md pour en savoir plus sur les JOIN, les CTE, les fonctions de fenêtre, les opérations JSON et bien plus encore.
Exploration du schéma
Requêtes d’introspection
PostgreSQL — liste des tables et des colonnes
SELECT nom_table, nom_colonne, type_de_données, est_nullable, valeur_par_défaut
FROM information_schema.columns
WHERE schéma_table = 'public'
ORDER BY nom_table, position_ordonnée;
PostgreSQL — clés étrangères
SELECT tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table, ccu.column_name AS foreign_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY' ;
MySQL — tailles des tables
SELECT nom_table, nombre_lignes,
ROUND(longueur_données / 1024 / 1024, 2) AS données_mb,
ROUND(longueur_index / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length DESC;
SQLite — sauvegarde du schéma
SELECT nom, sql FROM sqlite_master WHERE type = 'table' ORDER BY nom;
SQL Server — colonnes avec types
SELECT t.name AS table_name, c.name AS column_name,
ty.name AS data_type, c.max_length, c.is_nullable
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
ORDER BY t.name, c.column_id;
Génération de la documentation à partir du schéma
Utilisez scripts/schema_explorer.py pour générer une documentation au format Markdown ou JSON :
python scripts/schema_explorer.py --dialect postgres --tables all --format md
python scripts/schema_explorer.py --dialect mysql --tables users,orders --format json --json
Optimisation des requêtes
Workflow d’analyse EXPLAIN
- Exécutez EXPLAIN ANALYZE (PostgreSQL) ou EXPLAIN FORMAT=JSON (MySQL)
- Identifiez le nœud le plus coûteux — Seq Scan sur les grandes tables, Nested Loop avec des estimations de lignes élevées
- Vérifier s’il manque des index — balayages séquentiels sur des colonnes filtrées
- Recherchez les erreurs d’estimation — un écart entre le nombre de lignes prévu et réel indique que les statistiques sont obsolètes
- Évaluez l’ordre des JOIN — assurez-vous que le plus petit ensemble de résultats détermine la jointure
Liste de contrôle pour les recommandations d’index
- Colonnes dans les clauses WHERE présentant une sélectivité élevée
- Colonnes dans les conditions JOIN (clés étrangères)
- Colonnes dans ORDER BY lorsqu’elles sont combinées avec LIMIT
- Index composés correspondant à des prédicats WHERE sur plusieurs colonnes (en commençant par la colonne la plus sélective)
- Index partiels pour les requêtes avec des filtres constants (par exemple,
WHERE status = 'active') - Index couvrants pour éviter les recherches dans les tables pour les requêtes à forte intensité de lecture
Modèles de réécriture de requêtes
| Anti-modèle | Réécriture |
|---|---|
SELECT * FROM commandes |
SELECT id, status, total FROM orders (colonnes explicites) |
WHERE YEAR(created_at) = 2025 |
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01' (sargable) |
| Sous-requête corrélée dans SELECT | LEFT JOIN avec agrégation |
NOT IN (SELECT ...) avec des valeurs NULL |
NOT EXISTS (SELECT 1 ...) |
UNION (dedup) lorsque cela n'est pas nécessaire |
UNION ALL |
LIKE '%search%' |
Index de recherche plein texte (GIN/FULLTEXT) |
ORDER BY RAND() |
Échantillonnage aléatoire côté application ou TABLESAMPLE |
Détection N+1
Symptômes :
- Boucle d’application exécutant une requête par ligne parente
- Chargement différé par l'ORM des entités associées à l'intérieur d'une boucle
- Le journal des requêtes affiche des centaines de modèles SELECT identiques avec des ID différents
Solutions :
- Utiliser le chargement anticipé (
includedans Prisma,joinedloaddans SQLAlchemy) - Regrouper les requêtes avec
WHERE id IN (...) - Utiliser le modèle DataLoader pour les résolveurs GraphQL
Outil d'analyse statique
python scripts/query_optimizer.py --query "SELECT * FROM orders WHERE status = 'pending'" --dialect postgres
python scripts/query_optimizer.py --query queries.sql --dialect mysql --json
Consultez le fichier references/optimization_guide.md pour en savoir plus sur la lecture des plans EXPLAIN, les types d’index et la mise en pool des connexions.
Génération de migrations
Modèles de migration sans interruption de service
Ajout d’une colonne (sans risque)
-- Vers le haut
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Vers le bas
ALTER TABLE users DROP COLUMN phone;
Renommer une colonne (expansion-contraction)
-- Étape 1 : Ajouter une nouvelle colonne
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- Étape 2 : mise à jour rétrospective
UPDATE users SET full_name = name;
-- Étape 3 : déploiement de l'application lisant les deux colonnes
-- Étape 4 : déploiement de l'application écrivant uniquement dans la nouvelle colonne
-- Étape 5 : suppression de l'ancienne colonne
ALTER TABLE users DROP COLUMN name;
Ajout d’une colonne NOT NULL (séquence sécurisée)
-- Étape 1 : Ajouter une colonne pouvant contenir des valeurs NULL
ALTER TABLE orders ADD COLUMN region VARCHAR(50);
-- Étape 2 : Remplacement des anciennes valeurs par la valeur par défaut
UPDATE orders SET region = 'unknown' WHERE region IS NULL;
-- Étape 3 : Ajouter une contrainte
ALTER TABLE orders ALTER COLUMN region SET NOT NULL;
ALTER TABLE orders ALTER COLUMN region SET DEFAULT 'unknown';
Création d’un index (sans blocage, PostgreSQL)
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
Stratégies de remplissage des données
- Mises à jour par lots — traitement par tranches de 1 000 à 10 000 lignes pour éviter les conflits de verrouillage
- Tâches en arrière-plan — exécution asynchrone des réactualisations avec suivi de la progression
- Double écriture — écriture simultanée dans les anciennes et les nouvelles colonnes pendant la période de transition
- Requêtes de validation — vérification du nombre de lignes et de l’intégrité des données après chaque lot
Stratégies de restauration
Chaque migration doit disposer d’un script de restauration réversible. Pour les modifications irréversibles :
- Sauvegarde avant exécution —
pg_dumpdes tables concernées - Indicateurs de fonctionnalité — l’application peut basculer entre la lecture selon l’ancien et le nouveau schéma
- Tables miroirs — conserver une copie de la table d'origine pendant la fenêtre de migration
Outil de génération de migration
python scripts/migration_generator.py --change "ajouter le booléen email_verified à la table users" --dialect postgres --format sql
python scripts/migration_generator.py --change "renommer la colonne name en full_name dans la table customers" --dialect mysql --format alembic --json
Prise en charge de plusieurs bases de données
Différences entre dialectes
| Fonctionnalité | PostgreSQL | MySQL | SQLite | SQL Server |
|---|---|---|---|---|
| UPSERT | EN CAS DE CONFLIT, METTRE À JOUR |
EN CAS DE CLÉ DUPLIQUÉE, METTRE À JOUR |
EN CAS DE CONFLIT, METTRE À JOUR |
FUSION |
| Booléen | BOOLEAN natif |
TINYINT(1) |
INTEGER |
BIT |
| Auto-incrément | SÉRIE / GÉNÉRÉ |
AUTO_INCREMENT |
ENTIER CLÉ PRINCIPALE |
IDENTITY |
| JSON | JSONB (indexé) |
JSON |
Texte (ext) | NVARCHAR(MAX) |
| Tableau | Tableau natif |
Non pris en charge | Non pris en charge | Non pris en charge |
| CTE (récursif) | Prise en charge complète | 8.0+ | 3.8.3 | Prise en charge complète |
| Fonctions de fenêtre | Prise en charge complète | 8.0+ | 3.25.0+ | Prise en charge complète |
| Recherche en texte intégral | tsvector + GIN |
IndexFULLTEXT |
Extension FTS5 | Catalogue en texte intégral |
| LIMIT/OFFSET | LIMIT n OFFSET m |
LIMIT n OFFSET m |
LIMIT n OFFSET m |
OFFSET m LIGNES RÉCUPÉRER UNIQUEMENT LES n LIGNES SUIVANTES |
Conseils de compatibilité
- Utilisez toujours des requêtes paramétrées — cela empêche les injections SQL dans tous les dialectes
- Évitez les fonctions spécifiques à un dialecte dans le code partagé — intégrez-les dans une couche d’adaptation
- Testez les migrations sur le moteur cible —
information_schemavarie d’un moteur à l’autre - Utilisez le format de date ISO —
« AAAA-MM-JJ »fonctionne partout - Mettez les identifiants entre guillemets — utilisez des guillemets doubles (norme SQL) ou des guillemets inversés (MySQL)
Modèles ORM
Prisma
Définition du schéma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
modèle Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
Migrations: npx prisma migrate dev --name add_user_email
API de requête: prisma.user.findMany({ where: { email: { contains: '@' } }, include: { posts: true } })
Porte de secours SQL brut: prisma.$queryRaw\SELECT * FROM users WHERE id = ${userId}``
Drizzle
Définition « schéma d'abord »
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
});
Générateur de requêtes: db.select().from(users).where(eq(users.email, email))
Migrations: npx drizzle-kit generate:pg puis npx drizzle-kit push:pg
TypeORM
Décorateurs d'entités
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
Modèle de référentiel: userRepo.find({ where: { email }, relations: ['posts'] })
Migrations: npx typeorm migration:generate -n AddUserEmail
SQLAlchemy
Modèles déclaratifs
class User(Base) :
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False)
name = Column(String(255))
posts = relationship('Post', back_populates='author')
Gestion des sessions: toujours utiliser with Session() en tant que gestionnaire de contexte
Migrations Alembic: alembic revision --autogenerate -m "add user email"
Consultez le fichier references/orm_patterns.md pour des comparaisons côte à côte et les workflows de migration par ORM.
Intégrité des données
Stratégie de contraintes
- Clés primaires — chaque table doit en posséder une ; privilégiez les clés de substitution (sérial/UUID)
- Clés étrangères — garantissent l'intégrité référentielle ; définissez explicitement le comportement ON DELETE
- Contraintes UNIQUE — pour garantir l’unicité au niveau métier (e-mail, slug, clé API)
- Contraintes CHECK — valider les plages, les énumérations et les règles métier au niveau de la base de données
- NOT NULL — par défaut, utiliser NOT NULL ; ne permettre les valeurs nulles que lorsque cela est véritablement facultatif
Niveaux d'isolation des transactions
| Niveau | Lecture « sale » | Lecture non répétable | Lecture fantôme | Cas d’utilisation |
|---|---|---|---|---|
| LECTURE NON VALIDÉE | Oui | Oui | Oui | Jamais recommandé |
| LIRE AVEC ENGAGEMENT | Non | Oui | Oui | Par défaut pour PostgreSQL, OLTP général |
| LECTURE RÉPÉTABLE | Non | Non | Oui (InnoDB : Non) | Calculs financiers |
| SÉRIALISABLE | Non | Non | Non | Cohérence critique (facturation, stocks) |
Prévention des interblocages
- Ordre cohérent des verrous — acquérir toujours les verrous dans le même ordre de tables/lignes
- Transactions courtes — réduire au minimum le délai entre le premier verrou et la validation
- Verrous consultatifs — utiliser
pg_advisory_lock()pour la coordination au niveau de l’application - Logique de réessai — détecter les erreurs d’interblocage et réessayer avec un recul exponentiel
Sauvegarde et restauration
PostgreSQL
# Sauvegarde complète
pg_dump -Fc --no-owner dbname > backup.dump
# Restauration
pg_restore -d dbname --clean --no-owner backup.dump
# Récupération à un instant donné : configurer l’archivage WAL + restore_command
MySQL
# Sauvegarde complète
mysqldump --single-transaction --routines --triggers nom_base_de_données > sauvegarde.sql
# Restauration
mysql dbname < backup.sql
# Journal binaire pour la restauration à un instant donné (PITR) : mysqlbinlog --start-datetime="2025-01-01 00:00:00" binlog.000001
SQLite
# Sauvegarde (sécurisée en cas de lectures simultanées)
sqlite3 nom_base_de_données ".backup backup.db"
Bonnes pratiques en matière de sauvegarde
- Automatisation — cron ou minuteur systemd, jamais uniquement manuellement
- Tester les restaurations — une sauvegarde non testée n'est pas une sauvegarde
- Copies hors site — S3, GCS ou région distincte
- Politique de conservation — quotidienne pendant 7 jours, hebdomadaire pendant 4 semaines, mensuelle pendant 12 mois
- Surveillez la taille et la durée des sauvegardes — des changements soudains indiquent des problèmes
Anti-modèles
| Anti-modèle | Problème | Solution |
|---|---|---|
SELECT * |
Transfère des données inutiles, ne s'adapte pas aux modifications du schéma | Liste explicite des colonnes |
| Indices manquants sur les colonnes FK | JOIN lents et suppressions en cascade | Ajouter des index sur toutes les clés étrangères |
| Requêtes N+1 | 1 + N allers-retours vers la base de données | Chargement anticipé ou requêtes par lots |
| Coercition implicite de types | WHERE id = '123' empêche l'utilisation de l'index |
Correspondance des types dans les prédicats |
| Pas de pool de connexions | Épuisement des connexions en cas de charge élevée | PgBouncer, ProxySQL ou pool ORM |
| Requêtes sans limite | L'absence de LIMIT risque de renvoyer des millions de lignes | Toujours paginer |
| Stockage des montants en FLOAT | Erreurs d'arrondi | Utilisez DECIMAL(19,4) ou des centimes entiers |
| Tables « God tables » | Une table comportant plus de 50 colonnes | Normaliser ou utiliser un partitionnement vertical |
| Suppressions temporaires partout | Complique chaque requête avec WHERE deleted_at IS NULL |
Tables d’archivage ou « event sourcing » |
| Concaténation de chaînes brutes | Injection SQL | Requêtes paramétrées systématiquement |
Références croisées
| Compétence | Relation |
|---|---|
| Concepteur de bases de données | Architecture de schéma, analyse de normalisation, génération de diagrammes ER |
| concepteur-de-schémas-de-bases-de-données | Modélisation visuelle d’ERD, cartographie des relations |
| architecte-de-migration | Orchestration de migrations complexes en plusieurs étapes |
| vérificateur-de-conception-d'API | Garantir la conformité des points de terminaison API avec les modèles de requêtes |
| plateforme-d'observabilité | Surveillance des performances des requêtes, alertes en cas de requêtes lentes |
---
name: sql-database-assistant
description: Translate natural language into SQL queries, optimize database performance, generate migrations, explore schemas, and work with ORMs across PostgreSQL, MySQL, SQLite, and SQL Server.
---
# SQL Database Assistant - POWERFUL Tier Skill
## Overview
The operational companion to database design. While **database-designer** focuses on schema architecture and **database-schema-designer** handles ERD modeling, this skill covers the day-to-day: writing queries, optimizing performance, generating migrations, and bridging the gap between application code and database engines.
### Core Capabilities
- **Natural Language to SQL** — translate requirements into correct, performant queries
- **Schema Exploration** — introspect live databases across PostgreSQL, MySQL, SQLite, SQL Server
- **Query Optimization** — EXPLAIN analysis, index recommendations, N+1 detection, rewrite patterns
- **Migration Generation** — up/down scripts, zero-downtime strategies, rollback plans
- **ORM Integration** — Prisma, Drizzle, TypeORM, SQLAlchemy patterns and escape hatches
- **Multi-Database Support** — dialect-aware SQL with compatibility guidance
### Tools
| Script | Purpose |
|--------|---------|
| `scripts/query_optimizer.py` | Static analysis of SQL queries for performance issues |
| `scripts/migration_generator.py` | Generate migration file templates from change descriptions |
| `scripts/schema_explorer.py` | Generate schema documentation from introspection queries |
---
## Natural Language to SQL
### Translation Patterns
When converting requirements to SQL, follow this sequence:
1. **Identify entities** — map nouns to tables
2. **Identify relationships** — map verbs to JOINs or subqueries
3. **Identify filters** — map adjectives/conditions to WHERE clauses
4. **Identify aggregations** — map "total", "average", "count" to GROUP BY
5. **Identify ordering** — map "top", "latest", "highest" to ORDER BY + LIMIT
### Common Query Templates
**Top-N per group (window function)**
```sql
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees
) ranked WHERE rn <= 3;
```
**Running totals**
```sql
SELECT date, amount,
SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM transactions;
```
**Gap detection**
```sql
SELECT curr.id, curr.seq_num, prev.seq_num AS prev_seq
FROM records curr
LEFT JOIN records prev ON prev.seq_num = curr.seq_num - 1
WHERE prev.id IS NULL AND curr.seq_num > 1;
```
**UPSERT (PostgreSQL)**
```sql
INSERT INTO settings (key, value, updated_at)
VALUES ('theme', 'dark', NOW())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXCLUDED.updated_at;
```
**UPSERT (MySQL)**
```sql
INSERT INTO settings (key_name, value, updated_at)
VALUES ('theme', 'dark', NOW())
ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at);
```
> See references/query_patterns.md for JOINs, CTEs, window functions, JSON operations, and more.
---
## Schema Exploration
### Introspection Queries
**PostgreSQL — list tables and columns**
```sql
SELECT table_name, column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;
```
**PostgreSQL — foreign keys**
```sql
SELECT tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table, ccu.column_name AS foreign_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY';
```
**MySQL — table sizes**
```sql
SELECT table_name, table_rows,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length DESC;
```
**SQLite — schema dump**
```sql
SELECT name, sql FROM sqlite_master WHERE type = 'table' ORDER BY name;
```
**SQL Server — columns with types**
```sql
SELECT t.name AS table_name, c.name AS column_name,
ty.name AS data_type, c.max_length, c.is_nullable
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
ORDER BY t.name, c.column_id;
```
### Generating Documentation from Schema
Use `scripts/schema_explorer.py` to produce markdown or JSON documentation:
```bash
python scripts/schema_explorer.py --dialect postgres --tables all --format md
python scripts/schema_explorer.py --dialect mysql --tables users,orders --format json --json
```
---
## Query Optimization
### EXPLAIN Analysis Workflow
1. **Run EXPLAIN ANALYZE** (PostgreSQL) or **EXPLAIN FORMAT=JSON** (MySQL)
2. **Identify the costliest node** — Seq Scan on large tables, Nested Loop with high row estimates
3. **Check for missing indexes** — sequential scans on filtered columns
4. **Look for estimation errors** — planned vs actual rows divergence signals stale statistics
5. **Evaluate JOIN order** — ensure the smallest result set drives the join
### Index Recommendation Checklist
- Columns in WHERE clauses with high selectivity
- Columns in JOIN conditions (foreign keys)
- Columns in ORDER BY when combined with LIMIT
- Composite indexes matching multi-column WHERE predicates (most selective column first)
- Partial indexes for queries with constant filters (e.g., `WHERE status = 'active'`)
- Covering indexes to avoid table lookups for read-heavy queries
### Query Rewriting Patterns
| Anti-Pattern | Rewrite |
|-------------|---------|
| `SELECT * FROM orders` | `SELECT id, status, total FROM orders` (explicit columns) |
| `WHERE YEAR(created_at) = 2025` | `WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01'` (sargable) |
| Correlated subquery in SELECT | LEFT JOIN with aggregation |
| `NOT IN (SELECT ...)` with NULLs | `NOT EXISTS (SELECT 1 ...)` |
| `UNION` (dedup) when not needed | `UNION ALL` |
| `LIKE '%search%'` | Full-text search index (GIN/FULLTEXT) |
| `ORDER BY RAND()` | Application-side random sampling or `TABLESAMPLE` |
### N+1 Detection
**Symptoms:**
- Application loop that executes one query per parent row
- ORM lazy-loading related entities inside a loop
- Query log shows hundreds of identical SELECT patterns with different IDs
**Fixes:**
- Use eager loading (`include` in Prisma, `joinedload` in SQLAlchemy)
- Batch queries with `WHERE id IN (...)`
- Use DataLoader pattern for GraphQL resolvers
### Static Analysis Tool
```bash
python scripts/query_optimizer.py --query "SELECT * FROM orders WHERE status = 'pending'" --dialect postgres
python scripts/query_optimizer.py --query queries.sql --dialect mysql --json
```
> See references/optimization_guide.md for EXPLAIN plan reading, index types, and connection pooling.
---
## Migration Generation
### Zero-Downtime Migration Patterns
**Adding a column (safe)**
```sql
-- Up
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Down
ALTER TABLE users DROP COLUMN phone;
```
**Renaming a column (expand-contract)**
```sql
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- Step 2: Backfill
UPDATE users SET full_name = name;
-- Step 3: Deploy app reading both columns
-- Step 4: Deploy app writing only new column
-- Step 5: Drop old column
ALTER TABLE users DROP COLUMN name;
```
**Adding a NOT NULL column (safe sequence)**
```sql
-- Step 1: Add nullable
ALTER TABLE orders ADD COLUMN region VARCHAR(50);
-- Step 2: Backfill with default
UPDATE orders SET region = 'unknown' WHERE region IS NULL;
-- Step 3: Add constraint
ALTER TABLE orders ALTER COLUMN region SET NOT NULL;
ALTER TABLE orders ALTER COLUMN region SET DEFAULT 'unknown';
```
**Index creation (non-blocking, PostgreSQL)**
```sql
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
```
### Data Backfill Strategies
- **Batch updates** — process in chunks of 1000-10000 rows to avoid lock contention
- **Background jobs** — run backfills asynchronously with progress tracking
- **Dual-write** — write to old and new columns during transition period
- **Validation queries** — verify row counts and data integrity after each batch
### Rollback Strategies
Every migration must have a reversible down script. For irreversible changes:
1. **Backup before execution** — `pg_dump` the affected tables
2. **Feature flags** — application can switch between old/new schema reads
3. **Shadow tables** — keep a copy of the original table during migration window
### Migration Generator Tool
```bash
python scripts/migration_generator.py --change "add email_verified boolean to users" --dialect postgres --format sql
python scripts/migration_generator.py --change "rename column name to full_name in customers" --dialect mysql --format alembic --json
```
---
## Multi-Database Support
### Dialect Differences
| Feature | PostgreSQL | MySQL | SQLite | SQL Server |
|---------|-----------|-------|--------|------------|
| UPSERT | `ON CONFLICT DO UPDATE` | `ON DUPLICATE KEY UPDATE` | `ON CONFLICT DO UPDATE` | `MERGE` |
| Boolean | Native `BOOLEAN` | `TINYINT(1)` | `INTEGER` | `BIT` |
| Auto-increment | `SERIAL` / `GENERATED` | `AUTO_INCREMENT` | `INTEGER PRIMARY KEY` | `IDENTITY` |
| JSON | `JSONB` (indexed) | `JSON` | Text (ext) | `NVARCHAR(MAX)` |
| Array | Native `ARRAY` | Not supported | Not supported | Not supported |
| CTE (recursive) | Full support | 8.0+ | 3.8.3+ | Full support |
| Window functions | Full support | 8.0+ | 3.25.0+ | Full support |
| Full-text search | `tsvector` + GIN | `FULLTEXT` index | FTS5 extension | Full-text catalog |
| LIMIT/OFFSET | `LIMIT n OFFSET m` | `LIMIT n OFFSET m` | `LIMIT n OFFSET m` | `OFFSET m ROWS FETCH NEXT n ROWS ONLY` |
### Compatibility Tips
- **Always use parameterized queries** — prevents SQL injection across all dialects
- **Avoid dialect-specific functions in shared code** — wrap in adapter layer
- **Test migrations on target engine** — `information_schema` varies between engines
- **Use ISO date format** — `'YYYY-MM-DD'` works everywhere
- **Quote identifiers** — use double quotes (SQL standard) or backticks (MySQL)
---
## ORM Patterns
### Prisma
**Schema definition**
```prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
```
**Migrations**: `npx prisma migrate dev --name add_user_email`
**Query API**: `prisma.user.findMany({ where: { email: { contains: '@' } }, include: { posts: true } })`
**Raw SQL escape hatch**: `prisma.$queryRaw\`SELECT * FROM users WHERE id = ${userId}\``
### Drizzle
**Schema-first definition**
```typescript
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
});
```
**Query builder**: `db.select().from(users).where(eq(users.email, email))`
**Migrations**: `npx drizzle-kit generate:pg` then `npx drizzle-kit push:pg`
### TypeORM
**Entity decorators**
```typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
```
**Repository pattern**: `userRepo.find({ where: { email }, relations: ['posts'] })`
**Migrations**: `npx typeorm migration:generate -n AddUserEmail`
### SQLAlchemy
**Declarative models**
```python
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False)
name = Column(String(255))
posts = relationship('Post', back_populates='author')
```
**Session management**: Always use `with Session() as session:` context manager
**Alembic migrations**: `alembic revision --autogenerate -m "add user email"`
> See references/orm_patterns.md for side-by-side comparisons and migration workflows per ORM.
---
## Data Integrity
### Constraint Strategy
- **Primary keys** — every table must have one; prefer surrogate keys (serial/UUID)
- **Foreign keys** — enforce referential integrity; define ON DELETE behavior explicitly
- **UNIQUE constraints** — for business-level uniqueness (email, slug, API key)
- **CHECK constraints** — validate ranges, enums, and business rules at the DB level
- **NOT NULL** — default to NOT NULL; make nullable only when genuinely optional
### Transaction Isolation Levels
| Level | Dirty Read | Non-Repeatable Read | Phantom Read | Use Case |
|-------|-----------|-------------------|-------------|----------|
| READ UNCOMMITTED | Yes | Yes | Yes | Never recommended |
| READ COMMITTED | No | Yes | Yes | Default for PostgreSQL, general OLTP |
| REPEATABLE READ | No | No | Yes (InnoDB: No) | Financial calculations |
| SERIALIZABLE | No | No | No | Critical consistency (billing, inventory) |
### Deadlock Prevention
1. **Consistent lock ordering** — always acquire locks in the same table/row order
2. **Short transactions** — minimize time between first lock and commit
3. **Advisory locks** — use `pg_advisory_lock()` for application-level coordination
4. **Retry logic** — catch deadlock errors and retry with exponential backoff
---
## Backup & Restore
### PostgreSQL
```bash
# Full backup
pg_dump -Fc --no-owner dbname > backup.dump
# Restore
pg_restore -d dbname --clean --no-owner backup.dump
# Point-in-time recovery: configure WAL archiving + restore_command
```
### MySQL
```bash
# Full backup
mysqldump --single-transaction --routines --triggers dbname > backup.sql
# Restore
mysql dbname < backup.sql
# Binary log for PITR: mysqlbinlog --start-datetime="2025-01-01 00:00:00" binlog.000001
```
### SQLite
```bash
# Backup (safe with concurrent reads)
sqlite3 dbname ".backup backup.db"
```
### Backup Best Practices
- **Automate** — cron or systemd timer, never manual-only
- **Test restores** — untested backups are not backups
- **Offsite copies** — S3, GCS, or separate region
- **Retention policy** — daily for 7 days, weekly for 4 weeks, monthly for 12 months
- **Monitor backup size and duration** — sudden changes signal issues
---
## Anti-Patterns
| Anti-Pattern | Problem | Fix |
|-------------|---------|-----|
| `SELECT *` | Transfers unnecessary data, breaks on schema changes | Explicit column list |
| Missing indexes on FK columns | Slow JOINs and cascading deletes | Add indexes on all foreign keys |
| N+1 queries | 1 + N round trips to database | Eager loading or batch queries |
| Implicit type coercion | `WHERE id = '123'` prevents index use | Match types in predicates |
| No connection pooling | Exhausts connections under load | PgBouncer, ProxySQL, or ORM pool |
| Unbounded queries | No LIMIT risks returning millions of rows | Always paginate |
| Storing money as FLOAT | Rounding errors | Use `DECIMAL(19,4)` or integer cents |
| God tables | One table with 50+ columns | Normalize or use vertical partitioning |
| Soft deletes everywhere | Complicates every query with `WHERE deleted_at IS NULL` | Archive tables or event sourcing |
| Raw string concatenation | SQL injection | Parameterized queries always |
---
## Cross-References
| Skill | Relationship |
|-------|-------------|
| **database-designer** | Schema architecture, normalization analysis, ERD generation |
| **database-schema-designer** | Visual ERD modeling, relationship mapping |
| **migration-architect** | Complex multi-step migration orchestration |
| **api-design-reviewer** | Ensuring API endpoints align with query patterns |
| **observability-platform** | Query performance monitoring, slow query alerts |
Tous les fichiers
0 fichiersInstaller sql-database-assistant
Téléchargez et décompressez les fichiers de compétences dans votre répertoire .claude/skills/.
Télécharger le ZIPClonez le dépôt et copiez les fichiers de compétence dans votre projet.
git clone https://github.com/alirezarezvani/claude-skills/tree/main/engineering/skills/sql-database-assistant # Copy SKILL.md to your .claude/skills/ directory
Copier





Maison
