sql-database-assistant
alirezarezvani/claude-skills
자연어를 SQL 쿼리로 변환하고, 데이터베이스 성능을 최적화하며, 마이그레이션을 생성하고, 스키마를 탐색하며, PostgreSQL, MySQL, SQLite 및 SQL Server에서 ORM을 활용할 수 있습니다.
...모든 것을 확장하십시오SQL 데이터베이스 어시스턴트 - POWERFUL 티어 스킬
개요
데이터베이스 설계의 운영 지원자입니다. 데이터베이스 설계자는 스키마 아키텍처에 중점을 두고, 데이터베이스 스키마 설계자는 ERD 모델링을 담당하는 반면, 이 기술은 쿼리 작성, 성능 최적화, 마이그레이션 생성, 애플리케이션 코드와 데이터베이스 엔진 간의 격차 해소 등 일상적인 업무를 다룹니다.
핵심 역량
- 자연어에서 SQL로 변환 — 요구 사항을 정확하고 성능이 뛰어난 쿼리로 변환
- 스키마 탐색 — PostgreSQL, MySQL, SQLite, SQL Server 등 다양한 데이터베이스의 실행 중인 상태를 분석
- 쿼리 최적화 — EXPLAIN 분석, 인덱스 권장 사항, N+1 문제 탐지, 쿼리 재작성 패턴
- 마이그레이션 생성 — 업그레이드/다운그레이드 스크립트, 무중단 전략, 롤백 계획
- ORM 통합 — Prisma, Drizzle, TypeORM, SQLAlchemy 패턴 및 비상 대책
- 다중 데이터베이스 지원 — 호환성 지침이 포함된 방언 인식 SQL
도구
| 스크립트 | 목적 |
|---|---|
scripts/query_optimizer.py |
성능 문제를 파악하기 위한 SQL 쿼리의 정적 분석 |
scripts/migration_generator.py |
변경 내역 설명을 바탕으로 마이그레이션 파일 템플릿 생성 |
scripts/schema_explorer.py |
인트로스펙션 쿼리를 기반으로 스키마 문서 생성 |
자연어에서 SQL로
변환 패턴
요구 사항을 SQL로 변환할 때는 다음 순서를 따르십시오:
- 엔티티 식별 — 명사를 테이블에 매핑
- 관계 식별 — 동사를 JOIN 또는 하위 쿼리에 매핑
- 필터 식별 — 형용사/조건을 WHERE 절에 매핑
- 집계 식별 — “합계”, “평균”, “개수”를 GROUP BY에 매핑
- 정렬 기준 식별 — “top”, “latest”, “highest”를 ORDER BY + LIMIT에 매핑
일반적인 쿼리 템플릿
그룹별 상위 N개 (윈도우 함수)
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees
) ranked WHERE rn <= 3;
누적 합계
SELECT date, amount,
SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM transactions;
갭 감지
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);
JOIN, CTE, 윈도우 함수, JSON 연산 등에 대한 자세한 내용은 references/query_patterns.md를 참조하십시오.
스키마 탐색
자기 반성 쿼리
PostgreSQL — 테이블 및 열 나열
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 — 외래 키
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 — 테이블 크기
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 — 스키마 덤프
SELECT name, sql FROM sqlite_master WHERE type = 'table' ORDER BY name;
SQL Server — 데이터 유형이 지정된 열
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;
스키마에서 문서 생성
scripts/schema_explorer.py를 사용하여 마크다운 또는 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
쿼리 최적화
EXPLAIN 분석 워크플로우
- EXPLAIN ANALYZE (PostgreSQL) 또는 EXPLAIN FORMAT=JSON (MySQL)실행
- 가장 비용이 많이 드는 노드 식별 — 대용량 테이블에 대한 순차 스캔(Seq Scan), 높은 행 수 추정치가 있는 중첩 루프(Nested Loop)
- 누락된 인덱스 확인 — 필터링된 열에 대한 순차 스캔
- 추정 오류 확인 — 계획된 행 수와 실제 행 수의 차이가 통계 정보의 갱신이 필요함을 시사합니다
- JOIN 순서 평가 — 결과 집합이 가장 작은 조인이 먼저 수행되도록 확인
인덱스 권장 사항 체크리스트
- 선택성이 높은 WHERE 절 내의 열
- JOIN 조건에 포함된 열(외래 키)
- LIMIT과 함께 사용되는 ORDER BY 절의 열
- 다중 열 WHERE 조건과 일치하는 복합 인덱스(선택성이 가장 높은 열을 먼저)
- 상수 필터가 포함된 쿼리를 위한 부분 인덱스(예:
WHERE status = 'active') - 읽기 위주 쿼리에서 테이블 조회(lookup)를 피하기 위한 커버링 인덱스
쿼리 재작성 패턴
| 반패턴 | 재작성 |
|---|---|
SELECT * FROM orders |
SELECT id, status, total FROM orders (명시적 열) |
WHERE YEAR(created_at) = 2025 |
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01' (SARG 가능) |
| SELECT 절 내의 상관 하위 쿼리 | 집계 기능이 포함된 LEFT JOIN |
NULL이 포함된NOT IN (SELECT ...) |
NOT EXISTS (SELECT 1 ...) |
필요하지 않을 때UNION (중복 제거) |
UNION ALL |
LIKE '%search%' |
전체 텍스트 검색 인덱스 (GIN/FULLTEXT) |
ORDER BY RAND() |
애플리케이션 측 무작위 표본 추출 또는 TABLESAMPLE |
N+1 탐지
증상:
- 부모 행마다 하나의 쿼리를 실행하는 애플리케이션 루프
- 루프 내에서 ORM이 관련 엔티티를 지연 로딩함
- 쿼리 로그에 ID만 다른 수백 개의 동일한 SELECT 패턴이 표시됨
해결 방법:
- 이거어 로딩(Prisma의
include, SQLAlchemy의joinedload) 사용 WHERE id IN (...)을 사용하여 쿼리를 일괄 처리- GraphQL 리졸버에 DataLoader 패턴 사용
정적 분석 도구
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
EXPLAIN 실행 계획 해석, 인덱스 유형 및 연결 풀링에 대해서는 references/optimization_guide.md를 참조하십시오.
마이그레이션 생성
다운타임 없는 마이그레이션 패턴
열 추가 (안전)
-- 상향 마이그레이션
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- 하향 마이그레이션
ALTER TABLE users DROP COLUMN phone;
열 이름 변경 (확장-축소)
-- 1단계: 새 열 추가
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- 2단계: 데이터 백필
UPDATE users SET full_name = name;
-- 3단계: 두 열 모두를 읽는 앱 배포
-- 4단계: 새 열만 쓰는 앱 배포
-- 5단계: 기존 열 삭제
ALTER TABLE users DROP COLUMN name;
NOT NULL 열 추가 (안전한 순서)
-- 1단계: NULL 허용 열 추가
ALTER TABLE orders ADD COLUMN region VARCHAR(50);
-- 2단계: 기본값으로 데이터 보충
UPDATE orders SET region = 'unknown' WHERE region IS NULL;
-- 3단계: 제약 조건 추가
ALTER TABLE orders ALTER COLUMN region SET NOT NULL;
ALTER TABLE orders ALTER COLUMN region SET DEFAULT 'unknown';
인덱스 생성 (비차단, PostgreSQL)
CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
데이터 백필 전략
- 일괄 업데이트 — 잠금 경합을 피하기 위해 1,000~10,000행 단위로 처리
- 백그라운드 작업 — 진행 상황 추적과 함께 백필을 비동기적으로 실행
- 이중 기록 — 전환 기간 동안 기존 열과 새 열에 동시에 기록
- 검증 쿼리 — 각 배치 처리 후 행 수와 데이터 무결성을 확인
롤백 전략
모든 마이그레이션에는 되돌릴 수 있는 다운 스크립트가 반드시 있어야 합니다. 되돌릴 수 없는 변경의 경우:
- 실행 전 백업 — 영향을 받는 테이블에 대해
pg_dump를수행 - 기능 플래그 — 애플리케이션에서 기존/새로운 스키마 읽기 모드를 전환할 수 있음
- 섀도 테이블 — 마이그레이션 기간 동안 원본 테이블의 복사본을 유지
마이그레이션 생성기 도구
python scripts/migration_generator.py --change "users 테이블에 email_verified 부울형 열 추가" --dialect postgres --format sql
python scripts/migration_generator.py --change "customers 테이블의 name 열을 full_name으로 이름 변경" --dialect mysql --format alembic --json
다중 데이터베이스 지원
방언 차이
| 기능 | PostgreSQL | MySQL | SQLite | SQL Server |
|---|---|---|---|---|
| UPSERT | 충돌 시 업데이트 |
중복 키 시 업데이트 |
ON CONFLICT DO UPDATE |
병합 |
| 부울 | 네이티브 BOOLEAN |
TINYINT(1) |
INTEGER |
BIT |
| 자동 증가 | SERIAL / GENERATED |
AUTO_INCREMENT |
정수 기본 키 |
IDENTITY |
| JSON | JSONB (인덱싱됨) |
JSON |
텍스트 (ext) | NVARCHAR(MAX) |
| 배열 | 네이티브 ARRAY |
지원되지 않음 | 지원되지 않음 | 지원되지 않음 |
| CTE (재귀형) | 완전 지원 | 8.0 이상 | 3.8.3 이상 | 완전 지원 |
| 윈도우 함수 | 완전 지원 | 8.0 이상 | 3.25.0 이상 | 완전 지원 |
| 전체 텍스트 검색 | tsvector + GIN |
FULLTEXT 인덱스 |
FTS5 확장 | 전체 텍스트 카탈로그 |
| LIMIT/OFFSET | LIMIT n OFFSET m |
LIMIT n OFFSET m |
LIMIT n OFFSET m |
OFFSET m ROWS FETCH NEXT n ROWS ONLY |
호환성 관련 팁
- 항상 매개변수화된 쿼리를 사용하십시오 — 모든 방언에서 SQL 인젝션을 방지합니다
- 공유 코드에서는 방언별 함수 사용을 피하십시오 — 어댑터 계층으로 감싸십시오
- 대상 엔진에서 마이그레이션을 테스트하십시오 —
information_schema는엔진마다 다릅니다 - ISO 날짜 형식을 사용하십시오 —
'YYYY-MM-DD'는모든 곳에서 작동합니다 - 식별자는 따옴표로 묶으세요 — 큰따옴표(SQL 표준) 또는 백틱(MySQL)을 사용하세요
ORM 패턴
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
}
마이그레이션: npx prisma migrate dev --name add_user_email
쿼리 API: prisma.user.findMany({ where: { email: { contains: '@' } }, include: { posts: true } })
원시 SQL 이스케이프 해치: prisma.$queryRaw\SELECT * FROM users WHERE id = ${userId}``
Drizzle
스키마 우선 정의
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
});
쿼리 빌더: db.select().from(users).where(eq(users.email, email))
마이그레이션: npx drizzle-kit generate:pg, 그 다음 npx drizzle-kit push:pg
TypeORM
엔티티 데코레이터
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
리포지토리 패턴: userRepo.find({ where: { email }, relations: ['posts'] })
마이그레이션: npx typeorm migration:generate -n AddUserEmail
SQLAlchemy
선언적 모델
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')
세션 관리: 항상 with Session()을 세션 컨텍스트 매니저 로 사용
Alembic 마이그레이션: alembic revision --autogenerate -m "add user email"
ORM별 나란히 비교 및 마이그레이션 워크플로는 references/orm_patterns.md를 참조하십시오.
데이터 무결성
제약 조건 전략
- 주 키 — 모든 테이블에는 반드시 하나씩 있어야 하며, 대리 키(serial/UUID)를 우선적으로 사용하십시오
- 외래 키 — 참조 무결성을 강제 적용하고, ON DELETE 동작을 명시적으로 정의하십시오
- UNIQUE 제약 조건 — 비즈니스 수준의 고유성 확보를 위해 사용(이메일, 슬러그, API 키)
- CHECK 제약 조건 — DB 수준에서 범위, 열거형 및 비즈니스 규칙을 검증합니다
- NOT NULL — 기본적으로 NOT NULL로 설정하고, 진정으로 선택 사항인 경우에만 NULL 허용
트랜잭션 격리 수준
| 수준 | 더티 읽기 | 비반복 가능 읽기 | 팬텀 읽기 | 사용 사례 |
|---|---|---|---|---|
| 읽기 미확정 | 예 | 예 | 예 | 절대 권장하지 않음 |
| 커밋된 읽기 | 아니요 | 예 | 예 | PostgreSQL의 기본값, 일반 OLTP |
| 반복 가능 읽기 | 아니요 | 아니요 | 예 (InnoDB: 아니요) | 금융 계산 |
| SERIALIZABLE | 아니요 | 아니요 | 아니요 | 중요 일관성 (청구, 재고) |
교착 상태 방지
- 일관된 잠금 순서 — 항상 동일한 테이블/행 순서로 잠금을 획득
- 짧은 트랜잭션 — 첫 번째 잠금 획득부터 커밋까지의 시간을 최소화
- 권고용 잠금 — 애플리케이션 수준 조정을 위해
pg_advisory_lock()사용 - 재시도 로직 — 교착 상태 오류를 감지하고 지수적 백오프를 적용하여 재시도
백업 및 복원
PostgreSQL
# 전체 백업
pg_dump -Fc --no-owner dbname > backup.dump
# 복원
pg_restore -d dbname --clean --no-owner backup.dump
# 특정 시점 복구: WAL 아카이빙 + restore_command 구성
MySQL
# 전체 백업
mysqldump --single-transaction --routines --triggers dbname > backup.sql
# 복원
mysql dbname < backup.sql
# PITR용 바이너리 로그: mysqlbinlog --start-datetime="2025-01-01 00:00:00" binlog.000001
SQLite
# 백업 (동시 읽기 시에도 안전)
sqlite3 dbname ".backup backup.db"
백업 모범 사례
- 자동화 — cron 또는 systemd 타이머를 사용하며, 절대 수동으로만 수행하지 마십시오
- 복원 테스트 — 테스트되지 않은 백업은 백업이 아닙니다
- 오프사이트 사본 — S3, GCS 또는 별도의 리전
- 보존 정책 — 매일 7일, 매주 4주, 매월 12개월
- 백업 용량 및 소요 시간 모니터링 — 갑작스러운 변화는 문제의 신호
반대 사례
| 반패턴 | 문제 | 해결 방법 |
|---|---|---|
SELECT * |
불필요한 데이터를 전송하며, 스키마 변경 시 오류가 발생합니다 | 명시적 열 목록 |
| 외래 키(FK) 열에 인덱스가 없음 | JOIN 및 캐스케이딩 삭제 성능 저하 | 모든 외래 키에 인덱스 추가 |
| N+1 쿼리 | 데이터베이스와의 1 + N회 왕복 통신 | 이저 로딩 또는 일괄 쿼리 |
| 암시적 형 변환 | WHERE id = '123'은 인덱스 사용을 방해합니다 |
술어 내의 데이터형 일치 |
| 연결 풀링 없음 | 부하 시 연결 소진 | PgBouncer, ProxySQL 또는 ORM 풀 |
| 제한 없는 쿼리 | LIMIT가 없으면 수백만 개의 행이 반환될 위험이 있음 | 항상 페이지 분할을 적용하십시오 |
| 금액을 FLOAT 형식으로 저장 | 반올림 오류 | DECIMAL(19,4) 또는 정수 단위의 센트 사용 |
| 'God 테이블' | 50개 이상의 열을 가진 단일 테이블 | 정규화하거나 수직 분할을 사용하십시오 |
| 모든 곳에서 소프트 삭제 적용 | WHERE deleted_at IS NULL 조건으로 인해 모든 쿼리가 복잡해짐 |
테이블 아카이빙 또는 이벤트 소싱 |
| 원시 문자열 연결 | SQL 인젝션 | 항상 매개변수화된 쿼리 사용 |
상호 참조
| 기술 | 관계 |
|---|---|
| 데이터베이스 설계자 | 스키마 아키텍처, 정규화 분석, ERD 생성 |
| 데이터베이스 스키마 설계자 | 시각적 ERD 모델링, 관계 매핑 |
| 마이그레이션 아키텍트 | 복잡한 다단계 마이그레이션 오케스트레이션 |
| API 설계 검토자 | API 엔드포인트가 쿼리 패턴과 일치하도록 보장 |
| 가시성 플랫폼 | 쿼리 성능 모니터링, 느린 쿼리 알림 |
---
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 |
모든 파일
0개 파일sql-database-assistant 설치
스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어주세요.
ZIP 다운로드저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.
git clone https://github.com/alirezarezvani/claude-skills/tree/main/engineering/skills/sql-database-assistant # Copy SKILL.md to your .claude/skills/ directory
복사





집
