opción
HogarHogar Skill Gestión de bases de datos sql-database-assistant

sql-database-assistant

alirezarezvani/claude-skills alirezarezvani/claude-skills

Traduce lenguaje natural a consultas SQL, optimiza el rendimiento de las bases de datos, genera migraciones, explora esquemas y trabaja con ORM en PostgreSQL, MySQL, SQLite y SQL Server.

...Expandir todo
1
Tiempo actualizado 2 de septiembre de 2026

Asistente de bases de datos SQL - Habilidad de nivel «POWERFUL»

Descripción general

El complemento operativo para el diseño de bases de datos. Mientras que el diseñador de bases de datos se centra en la arquitectura del esquema y el diseñador de esquemas de bases de datos se encarga del modelado ERD, esta competencia abarca el día a día: escribir consultas, optimizar el rendimiento, generar migraciones y tender puentes entre el código de la aplicación y los motores de bases de datos.

Capacidades principales

  • De lenguaje natural a SQL: traduce los requisitos en consultas correctas y eficaces
  • Exploración de esquemas: analiza bases de datos activas en PostgreSQL, MySQL, SQLite y SQL Server
  • Optimización de consultas: análisis EXPLAIN, recomendaciones de índices, detección de N+1, patrones de reescritura
  • Generación de migraciones: scripts de actualización y restauración, estrategias sin tiempo de inactividad, planes de reversión
  • Integración con ORM: Prisma, Drizzle, TypeORM, patrones de SQLAlchemy y soluciones alternativas
  • Compatibilidad con múltiples bases de datos: SQL sensible a los dialectos con orientación sobre compatibilidad

Herramientas

Script Propósito
scripts/query_optimizer.py Análisis estático de consultas SQL para detectar problemas de rendimiento
scripts/migration_generator.py Generar plantillas de archivos de migración a partir de descripciones de cambios
scripts/schema_explorer.py Generar documentación del esquema a partir de consultas de introspección

De lenguaje natural a SQL

Patrones de traducción

Al convertir los requisitos a SQL, sigue esta secuencia:

  1. Identifica las entidades: asigna los sustantivos a tablas
  2. Identificar relaciones: asignar los verbos a JOIN o subconsultas
  3. Identificar filtros: asignar adjetivos y condiciones a cláusulas WHERE
  4. Identificar agregaciones: asignar «total», «media» y «recuento» a GROUP BY
  5. Identifica los ordenamientos: asigna «top», «más recientes» y «más altos» a ORDER BY + LIMIT

Plantillas de consulta comunes

Top-N por grupo (función de ventana)

SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
  FROM employees
) ranked WHERE rn <= 3;

Totales acumulados

SELECT fecha, importe,
  SUM(importe) OVER (ORDER BY fecha ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total_acumulado
FROM transacciones;

Detección de huecos

SELECT id_actual, num_sec_actual, num_sec_anterior AS num_sec_anterior
FROM registros_actuales
LEFT JOIN registros_anteriores ON num_sec_anterior = id_actual - 1
WHERE id_anterior IS NULL AND num_secuencial_actual > 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);

Consulta references/query_patterns.md para obtener información sobre JOIN, CTE, funciones de ventana, operaciones JSON y mucho más.

Exploración del esquema

Consultas de introspección

PostgreSQL: listar tablas y columnas

SELECT nombre_tabla, nombre_columna, tipo_datos, es_nulo, valor_predeterminado_columna
FROM information_schema.columns
WHERE esquema_tabla = 'public'
ORDER BY nombre_tabla, posición_ordinal;

PostgreSQL — claves externas

SELECT tc.nombre_tabla, kcu.nombre_columna,
  ccu.nombre_tabla AS tabla_externa, ccu.nombre_columna AS columna_externa
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 — tamaños de las tablas

SELECT nombre_tabla, filas_tabla,
  ROUND(longitud_datos / 1024 / 1024, 2) AS datos_mb,
  ROUND(longitud_índice / 1024 / 1024, 2) AS índice_mb
FROM information_schema.tables
WHERE esquema_tabla = DATABASE()
ORDER BY longitud_datos DESC;

SQLite — volcado del esquema

SELECT name, sql FROM sqlite_master WHERE type = 'table' ORDER BY name;

SQL Server — columnas con tipos

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;

Generación de documentación a partir del esquema

Utiliza scripts/schema_explorer.py para generar documentación en formato Markdown o 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

Optimización de consultas

Flujo de trabajo del análisis EXPLAIN

  1. Ejecuta EXPLAIN ANALYZE (PostgreSQL) o EXPLAIN FORMAT=JSON (MySQL)
  2. Identifica el nodo más costoso: Seq Scan en tablas grandes, Nested Loop con estimaciones elevadas de filas
  3. Comprueba si faltan índices: escaneos secuenciales en columnas filtradas
  4. Busca errores de estimación: la divergencia entre las filas planificadas y las reales indica que las estadísticas están desactualizadas
  5. Evalúa el orden de las uniones (JOIN): asegúrate de que el conjunto de resultados más pequeño sea el que impulse la unión

Lista de comprobación de recomendaciones de índices

  • Columnas en cláusulas WHERE con alta selectividad
  • Columnas en condiciones JOIN (claves externas)
  • Columnas en ORDER BY cuando se combinan con LIMIT
  • Índices compuestos que coinciden con predicados WHERE de varias columnas (primero la columna más selectiva)
  • Índices parciales para consultas con filtros constantes (p. ej., WHERE status = 'active')
  • Índices de cobertura para evitar búsquedas en tablas en consultas con gran volumen de lecturas

Patrones de reescritura de consultas

Antipatrón Reescritura
SELECT * FROM pedidos SELECT id, status, total FROM pedidos (columnas explícitas)
WHERE YEAR(created_at) = 2025 WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01' (sargable)
Subconsulta correlacionada en SELECT LEFT JOIN con agregación
NOT IN (SELECT ...) con valores NULL NOT EXISTS (SELECT 1 ...)
UNION (dedup) cuando no es necesario UNION ALL
LIKE '%búsqueda%' Índice de búsqueda de texto completo (GIN/FULLTEXT)
ORDER BY RAND() Muestreo aleatorio en el lado de la aplicación o TABLESAMPLE

Detección de N+1

Síntomas:

  • Bucle de la aplicación que ejecuta una consulta por cada fila principal
  • Carga diferida por parte del ORM de entidades relacionadas dentro de un bucle
  • El registro de consultas muestra cientos de patrones SELECT idénticos con diferentes ID

Soluciones:

  • Utilizar la carga inmediata (include en Prisma, joinedload en SQLAlchemy)
  • Agrupar consultas con WHERE id IN (...)
  • Utilizar el patrón DataLoader para los resolvers de GraphQL

Herramienta de análisis estático

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

Consulta references/optimization_guide.md para obtener información sobre la lectura del plan EXPLAIN, los tipos de índices y el uso de grupos de conexiones.

Generación de migraciones

Patrones de migración sin tiempo de inactividad

Añadir una columna (seguro)

-- Hacia arriba
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Hacia abajo
ALTER TABLE users DROP COLUMN phone;

Cambiar el nombre de una columna (expandir-contraer)

-- Paso 1: Añadir una nueva columna
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- Paso 2: Rellenar los datos históricos
UPDATE users SET full_name = name;
-- Paso 3: Implementar la aplicación que lee ambas columnas
-- Paso 4: Implementar la aplicación que escribe solo en la nueva columna
-- Paso 5: Eliminar la columna antigua
ALTER TABLE users DROP COLUMN name;

Añadir una columna NOT NULL (secuencia segura)

-- Paso 1: Añadir una columna que admita valores nulos
ALTER TABLE orders ADD COLUMN region VARCHAR(50);
-- Paso 2: Rellenar los datos con el valor por defecto
UPDATE orders SET region = 'unknown' WHERE region IS NULL;
-- Paso 3: Añadir restricción
ALTER TABLE orders ALTER COLUMN region SET NOT NULL;
ALTER TABLE orders ALTER COLUMN region SET DEFAULT 'unknown';

Creación de índices (sin bloqueo, PostgreSQL)

CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);

Estrategias de rellenado de datos

  • Actualizaciones por lotes: procesar en bloques de entre 1.000 y 10.000 filas para evitar conflictos de bloqueo
  • Tareas en segundo plano: ejecutan los rellenos de forma asíncrona con seguimiento del progreso
  • Escritura dual: se escribe en las columnas antiguas y nuevas durante el periodo de transición
  • Consultas de validación: verifican el recuento de filas y la integridad de los datos tras cada lote

Estrategias de reversión

Cada migración debe contar con un script de reversión. Para cambios irreversibles:

  1. Copia de seguridad antes de la ejecución: realizar un pg_dump de las tablas afectadas
  2. Indicadores de funcionalidad: la aplicación puede alternar entre lecturas del esquema antiguo y del nuevo
  3. Tablas «sombra »: conservar una copia de la tabla original durante el periodo de migración

Herramienta generadora de migraciones

python scripts/migration_generator.py --change "añadir el valor booleano email_verified a la tabla users" --dialect postgres --format sql
python scripts/migration_generator.py --change "renombrar la columna 'name' a 'full_name' en 'customers'" --dialect mysql --format alembic --json

Compatibilidad con múltiples bases de datos

Diferencias entre dialectos

Característica PostgreSQL MySQL SQLite SQL Server
UPSERT EN CASO DE CONFLICTO, ACTUALIZAR EN CASO DE CLAVE DUPLICADA, ACTUALIZAR EN CASO DE CONFLICTO, ACTUALIZAR MERGE
Booleano BOOLEANO nativo TINYINT(1) ENTERO BIT
Autoincremento SERIAL / GENERADO AUTO_INCREMENT ENTERO CLAVE PRIMARIA IDENTITY
JSON JSONB (indexado) JSON Texto (ext) NVARCHAR(MAX)
Matriz Matriz nativa No compatible No compatible No compatible
CTE (recursivo) Compatibilidad total 8.0+ 3.8.3+ Compatibilidad total
Funciones de ventana Compatibilidad total 8.0+ 3.25.0+ Compatibilidad total
Búsqueda de texto completo tsvector + GIN ÍndiceFULLTEXT Extensión FTS5 Catálogo de texto completo
LIMIT/OFFSET LÍMITE n DESPLAZAMIENTO m LIMIT n OFFSET m LIMIT n OFFSET m DESPLAZAMIENTO m FILAS RECUPERAR SOLO LAS n FILAS SIGUIENTES

Consejos de compatibilidad

  • Utiliza siempre consultas parametrizadas: evitan la inyección de SQL en todos los dialectos
  • Evita las funciones específicas de cada dialecto en el código compartido: envuélvelas en una capa adaptadora
  • Prueba las migraciones en el motor de destino: «information_schema» varía según el motor
  • Utiliza el formato de fecha ISO: «AAAA-MM-DD» funciona en todas partes
  • Encierra los identificadores entre comillas: utiliza comillas dobles (estándar SQL) o comillas invertidas (MySQL)

Patrones ORM

Prisma

Definición del esquema

modelo User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

modelo Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}

Migraciones: npx prisma migrate dev --name add_user_email API de consulta: prisma.user.findMany({ where: { email: { contains: '@' } }, include: { posts: true } }) Escape de SQL sin procesar: prisma.$queryRaw\SELECT * FROM users WHERE id = ${userId}``

Drizzle

Definición «schema-first»

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  name: text('name'),
  createdAt: timestamp('created_at').defaultNow(),
});

Generador de consultas: db.select().from(users).where(eq(users.email, email)) Migraciones: npx drizzle-kit generate:pg y, a continuación, npx drizzle-kit push:pg

TypeORM

Decoradores de entidades

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

Patrón de repositorio: userRepo.find({ where: { email }, relations: ['posts'] }) Migraciones: npx typeorm migration:generate -n AddUserEmail

SQLAlchemy

Modelos declarativos

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')

Gestión de sesiones: Utilizar siempre with Session() como gestor de contexto Migraciones de Alembic: alembic revision --autogenerate -m "añadir correo electrónico de usuario"

Consulta references/orm_patterns.md para ver comparativas detalladas y flujos de trabajo de migración por ORM.

Integridad de los datos

Estrategia de restricciones

  • Claves primarias: cada tabla debe tener una; se recomienda utilizar claves sustitutivas (seriales/UUID)
  • Claves externas: garantizan la integridad referencial; define explícitamente el comportamiento ON DELETE
  • Restricciones UNIQUE: para garantizar la unicidad a nivel empresarial (correo electrónico, slug, clave API)
  • Restricciones CHECK: validan rangos, enumeraciones y reglas de negocio a nivel de la base de datos
  • NOT NULL: establecer NOT NULL como valor por defecto; permitir valores nulos solo cuando sean realmente opcionales

Niveles de aislamiento de transacciones

Nivel Lectura sucia Lectura no repetible Lectura fantasma Caso de uso
LECTURA NO CONFIRMADA Nunca recomendado
LEER CON COMPROMISO No Predeterminado para PostgreSQL, OLTP general
LECTURA REPETIBLE No No Sí (InnoDB: No) Cálculos financieros
SERIALIZABLE No No No Consistencia crítica (facturación, inventario)

Prevención de interbloqueos

  1. Orden coherente de los bloqueos: adquirir siempre los bloqueos en el mismo orden de tablas y filas
  2. Transacciones cortas: minimizar el tiempo entre el primer bloqueo y la confirmación
  3. Bloqueos de aviso: utilizar pg_advisory_lock() para la coordinación a nivel de aplicación
  4. Lógica de reintento: detectar errores de interbloqueo y volver a intentarlo con retroceso exponencial

Copia de seguridad y restauración

PostgreSQL

# Copia de seguridad completa
pg_dump -Fc --no-owner nombre_base_datos > copia_de_seguridad.dump
# Restauración
pg_restore -d nombre_base_datos --clean --no-owner copia_de_seguridad.dump
# Recuperación a un momento determinado: configurar el archivado WAL + restore_command

MySQL

# Copia de seguridad completa
mysqldump --single-transaction --routines --triggers nombre_bd > copia_seg.sql
# Restauración
mysql dbname < backup.sql
# Registro binario para PITR: mysqlbinlog --start-datetime="2025-01-01 00:00:00" binlog.000001

SQLite

# Copia de seguridad (segura con lecturas simultáneas)
sqlite3 nombre_base_datos ".backup backup.db"

Buenas prácticas de copia de seguridad

  • Automatizar: mediante cron o el temporizador de systemd; nunca solo de forma manual
  • Probar las restauraciones: las copias de seguridad sin probar no son copias de seguridad
  • Copias externas: S3, GCS o una región diferente
  • Política de retención: diaria durante 7 días, semanal durante 4 semanas, mensual durante 12 meses
  • Supervisa el tamaño y la duración de las copias de seguridad: los cambios repentinos indican problemas

Antipatrones

Antipatrón Problema Solución
SELECT * Transfiere datos innecesarios y falla ante cambios en el esquema Lista explícita de columnas
Faltan índices en las columnas FK JOIN lentos y eliminaciones en cascada Añadir índices en todas las claves externas
Consultas N+1 1 + N idas y vueltas a la base de datos Carga anticipada o consultas por lotes
Coerción de tipos implícita WHERE id = '123' impide el uso del índice Tipos de coincidencia en los predicados
Sin agrupación de conexiones Agota las conexiones bajo carga PgBouncer, ProxySQL o el grupo de ORM
Consultas sin límites La ausencia de LIMIT conlleva el riesgo de devolver millones de filas Paginación obligatoria
Almacenar cantidades monetarias como FLOAT Errores de redondeo Utilizar DECIMAL(19,4) o céntimos enteros
Tablas «divinas» Una tabla con más de 50 columnas Normalizar o utilizar partición vertical
Eliminaciones temporales en todas partes Complica cada consulta con WHERE deleted_at IS NULL Tablas de archivo o «event sourcing»
Concatenación de cadenas sin procesar Inyección SQL Consultas parametrizadas siempre

Referencias cruzadas

Habilidad Relación
diseñador de bases de datos Arquitectura de esquemas, análisis de normalización, generación de diagramas ERD
diseñador-de-esquemas-de-bases-de-datos Modelado visual de diagramas ERD, mapeo de relaciones
arquitecto-de-migración Coordinación de migraciones complejas de varios pasos
revisor-de-diseño-de-API Garantizar que los puntos finales de la API se ajusten a los patrones de consulta
plataforma de observabilidad Supervisión del rendimiento de las consultas; alertas de consultas lentas
Ver en GitHub
---
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 |

Todos los archivos

0 archivos

Instalar sql-database-assistant

Descarga y descomprime los archivos de habilidades en tu directorio .claude/skills/.

Descargar ZIP

Clona el repositorio y copia los archivos de la habilidad a tu proyecto.

git clone https://github.com/alirezarezvani/claude-skills/tree/main/engineering/skills/sql-database-assistant # Copy SKILL.md to your .claude/skills/ directory

Copiar Copiar
Configuración rápida: Copia la carpeta de la habilidad en .claude/skills/ Claude detectará y utilizará automáticamente la habilidad

Habilidades relacionadas

microservices-patterns
Tiempo actualizado 29 de junio de 2026
jpa-patterns
Tiempo actualizado 30 de junio de 2026
fabric-lakehouse
Tiempo actualizado 30 de junio de 2026
prisma-expert
Tiempo actualizado 29 de junio de 2026
OR