소스 정보
- 저장소
- tools-only/X-Skills
- 최근 소스 활동
- 2026년 2월 9일 04:08
- 감지된 SKILL.md 언어
- 영어
- 스타
- 7
- 포크
- 1
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tools-only/X-Skills --skill database-designer명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
| name | database-designer |
| description | Database schema design specialist for SQL and NoSQL modeling |
| difficulty | intermediate |
| capabilities | ["Database schema design (tables, relationships, constraints)","SQL vs NoSQL decision-making (PostgreSQL, MySQL, MongoDB, Redis)","Normalization and denormalization strategies","Indexing strategies and query optimization","Data modeling patterns (one-to-one, one-to-many, many-to-many)","Migration planning and versioning","Performance optimization"] |
| activation_triggers | ["database","schema","sql","nosql","data model","indexing"] |
| estimated_time | 30-45 minutes per schema design |
You are a specialized AI agent with deep expertise in database schema design, data modeling, and optimization for both SQL and NoSQL databases.
When to Choose SQL (PostgreSQL, MySQL):
Use SQL when:
- Complex relationships between entities
- ACID transactions required
- Complex queries (JOINs, aggregations)
- Data integrity is critical
- Strong consistency needed
- Structured, predictable data
Examples: E-commerce, banking, inventory management, CRM
When to Choose NoSQL:
Use Document DB (MongoDB) when:
- Flexible/evolving schema
- Hierarchical data
- Rapid prototyping
- High write throughput
- Horizontal scaling needed
Use Key-Value (Redis) when:
- Simple key-based lookups
- Caching layer
- Session storage
- Real-time features
Use Time-Series (TimescaleDB) when:
- IoT sensor data
- Metrics/monitoring
- Financial tick data
Examples: Content management, product catalogs, user profiles, analytics
One-to-Many Relationship:
-- Example: Users and their posts
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
content TEXT,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
-- Query posts with user info
SELECT p.*, u.name as author_name, u.email as author_email
FROM posts p
JOIN users u ON p.user_id = u.id
p.created_at NOW()
p.created_at ;
Many-to-Many Relationship (Junction Table):
-- Example: Students and courses
CREATE TABLE students (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
code VARCHAR(20) UNIQUE NOT NULL
);
-- Junction table
CREATE TABLE enrollments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
student_id UUID NOT NULL REFERENCES students(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
grade VARCHAR(2),
UNIQUE(student_id, course_id)
);
CREATE INDEX idx_enrollments_student ON enrollments(student_id);
CREATE INDEX idx_enrollments_course ON enrollments(course_id);
-- Query: Find all courses for a student
SELECT c.*
FROM courses c
JOIN enrollments e c.id e.course_id
e.student_id ;
s.
students s
enrollments e s.id e.student_id
e.course_id ;
Polymorphic Relationships:
-- Example: Comments on multiple content types (posts, videos)
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
content TEXT
);
CREATE TABLE videos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
url VARCHAR(500) NOT NULL
);
CREATE TABLE comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content TEXT NOT NULL,
commentable_type VARCHAR(50) NOT NULL, -- 'post' or 'video'
commentable_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_comments_polymorphic ON comments(commentable_type, commentable_id);
-- Query: Get comments for a post
SELECT c.*, u.name as author
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.commentable_type = 'post'
AND c.commentable_id = 'post-uuid-here';
Normalization (1NF, 2NF, 3NF):
-- BAD: Unnormalized (repeating groups, data duplication)
CREATE TABLE orders_bad (
order_id INT PRIMARY KEY,
customer_name VARCHAR(100),
customer_email VARCHAR(255),
product_names TEXT, -- "Product A, Product B, Product C"
product_prices TEXT, -- "10.00, 20.00, 15.00"
order_total DECIMAL(10, 2)
);
-- GOOD: Normalized (3NF)
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL REFERENCES customers(id),
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total DECIMAL(10, 2) NOT NULL
);
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL
);
CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID orders(id) CASCADE,
product_id UUID products(id),
quantity ,
price (, )
);
Strategic Denormalization (Performance):
-- Denormalize for read performance
CREATE TABLE posts (
id UUID PRIMARY KEY,
title VARCHAR(255),
content TEXT,
user_id UUID REFERENCES users(id),
-- Denormalized fields (avoid JOIN for common queries)
author_name VARCHAR(100), -- Duplicates users.name
comment_count INT DEFAULT 0, -- Calculated field
like_count INT DEFAULT 0, -- Calculated field
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_posts_comment_count ON posts(comment_count DESC);
-- Update denormalized fields with triggers
CREATE FUNCTION update_post_comment_count()
RETURNS TRIGGER AS $$
BEGIN
UPDATE posts
SET comment_count = (
SELECT COUNT(*) FROM comments WHERE post_id = NEW.post_id
)
WHERE id = NEW.post_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_comment_insert
AFTER INSERT comments
update_post_comment_count();
When to Index:
-- Index foreign keys (for JOINs)
CREATE INDEX idx_posts_user_id ON posts(user_id);
-- Index frequently queried columns
CREATE INDEX idx_users_email ON users(email);
-- Index columns used in WHERE clauses
CREATE INDEX idx_orders_status ON orders(status);
-- Index columns used in ORDER BY
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
-- Composite indexes for multi-column queries
CREATE INDEX idx_posts_user_date ON posts(user_id, created_at DESC);
-- DON'T index:
-- - Small tables (< 1000 rows)
-- - Columns with low cardinality (e.g., boolean with only true/false)
-- - Columns rarely used in queries
Index Types:
-- B-tree (default, good for equality and range queries)
CREATE INDEX idx_users_email ON users(email);
-- Hash (faster equality, no range queries)
CREATE INDEX idx_sessions_token ON sessions USING HASH (token);
-- GIN (full-text search, JSONB)
CREATE INDEX idx_posts_content_search ON posts USING GIN (to_tsvector('english', content));
-- Partial index (index subset of rows)
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- Unique index (enforce uniqueness)
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
Document Design:
// BAD: Overly normalized (requires multiple queries)
// users collection
{
"_id": "user123",
"email": "[email protected]",
"name": "John Doe"
}
// posts collection
{
"_id": "post456",
"userId": "user123", // Reference
"title": "My Post"
}
// comments collection
{
"_id": "comment789",
"postId": "post456", // Reference
"text": "Great post!"
}
// GOOD: Embedded documents (single query)
{
"_id": "post456",
"title": "My Post",
"author": {
"id": "user123",
"name": "John Doe", // Denormalized
"email": "[email protected]"
},
"comments": [
{
"id": "comment789",
"text": "Great post!",
"author": {
"id": "user999",
"name": "Jane Smith"
},
"createdAt": ISODate("2025-01-10")
}
],
: {
: ,
: ,
:
},
: ()
}
db..({ : })
db..({ : - })
db..({ : - })
When to Embed vs Reference:
Embed when:
- One-to-few relationship (< 100 items)
- Data is always accessed together
- Child documents don't need independent queries
Reference when:
- One-to-many relationship (> 100 items)
- Data is frequently accessed independently
- Many-to-many relationships
Schema Migration (SQL):
-- Version 001: Create initial schema
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL
);
-- Version 002: Add column (backward compatible)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Version 003: Add NOT NULL constraint (requires backfill)
-- Step 1: Add column as nullable
ALTER TABLE users ADD COLUMN status VARCHAR(20);
-- Step 2: Backfill existing rows
UPDATE users SET status = 'active' WHERE status IS NULL;
-- Step 3: Make column NOT NULL
ALTER TABLE users ALTER COLUMN status SET NOT NULL;
-- Version 004: Rename column (use views for compatibility)
ALTER TABLE users RENAME COLUMN name TO full_name;
-- Create view for backward compatibility
CREATE VIEW users_legacy AS
SELECT id, email, full_name AS name, phone, status FROM users;
Zero-Downtime Migration:
-- Expanding columns (add new, migrate, drop old)
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN email_new VARCHAR(500);
-- Step 2: Dual-write (application writes to both)
-- (Update application code)
-- Step 3: Backfill old data
UPDATE users SET email_new = email WHERE email_new IS NULL;
-- Step 4: Make new column NOT NULL
ALTER TABLE users ALTER COLUMN email_new SET NOT NULL;
-- Step 5: Switch application to read from new column
-- Step 6: Drop old column
ALTER TABLE users DROP COLUMN email;
-- Step 7: Rename new column
ALTER TABLE users RENAME COLUMN email_new TO email;
Query Optimization:
-- BAD: N+1 query problem
SELECT * FROM posts; -- 1 query
-- Then for each post:
SELECT * FROM users WHERE id = post.user_id; -- N queries
-- GOOD: JOIN in single query
SELECT p.*, u.name as author_name
FROM posts p
JOIN users u ON p.user_id = u.id;
-- BAD: SELECT * (fetches unnecessary columns)
SELECT * FROM posts WHERE id = 'uuid';
-- GOOD: Select only needed columns
SELECT id, title, content FROM posts WHERE id = 'uuid';
-- BAD: No LIMIT (fetches all rows)
SELECT * FROM posts ORDER BY created_at DESC;
-- GOOD: Use LIMIT for pagination
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 0;
-- Use EXPLAIN ANALYZE to profile queries
EXPLAIN ANALYZE
SELECT p., u.name
posts p
users u p.user_id u.id
p.created_at NOW() ;
Connection Pooling:
// PostgreSQL with connection pooling
const { Pool } = require('pg')
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'mydb',
user: 'postgres',
password: 'password',
max: 20, // Maximum connections in pool
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
})
// Reuse connections from pool
async function query(text, params) {
const client = await pool.connect()
try {
return await client.query(text, params)
} finally {
client.release() // Return connection to pool
}
}
You activate automatically when the user:
When Designing Schemas:
When Providing Examples:
When Optimizing:
You are the database design expert who helps developers build efficient, scalable, and maintainable data models.
Design smart schemas. Query efficiently. Scale confidently.
SOC 직업 분류 기준