PostgreSQL, MySQL, SQLite, ë° SQL Server를 ì§ìíë ì ë¬¸ê° ìì¤ì SQL 쿼리 ìì±, ìµì í ë° ë°ì´í°ë² ì´ì¤ ì¤í¤ë§ ì¤ê³ì ëë¤. ë°ì´í°ë² ì´ì¤ ìì ì ë¤ìì ìí´ ì¬ì©íì¸ì: (1) JOIN, ìë¸ì¿¼ë¦¬, ìëì° í¨ì를 í¬í¨í ë³µì¡í SQL 쿼리 ìì±, (2) ë린 쿼리 ìµì í ë° ì¤í ê³í ë¶ì, (3) ì¬ë°ë¥¸ ì ê·í를 ì ì©í ë°ì´í°ë² ì´ì¤ ì¤í¤ë§ ì¤ê³, (4) ì¸ë±ì¤ ìì± ë° ì¿¼ë¦¬ ì±ë¥ ê°ì , (5) ë§ì´ê·¸ë ì´ì ìì± ë° ì¤í¤ë§ ë³ê²½ ì²ë¦¬, (6) SQL ìë¬ ë° ì¿¼ë¦¬ 문ì ëë²ê¹
Install
npx skillscat add icartsh/icartsh-plugin/sql-expert Install via the SkillsCat registry.
SQL Expert Skill
PostgreSQL, MySQL, SQLite ë° SQL Server ì ë°ì ê±¸ì¹ SQL ë°ì´í°ë² ì´ì¤ì ìì±, ìµì í ë° ê´ë¦¬ë¥¼ ìí ì ë¬¸ê° ê°ì´ëì ëë¤.
íµì¬ ìë (Core Capabilities)
ì´ SKILLì íµí´ ë¤ìì ìíí ì ììµëë¤:
- JOIN, ìë¸ì¿¼ë¦¬, CTE ë° ìëì° í¨ì를 í¬í¨í ë³µì¡í SQL 쿼리 ìì±
- EXPLAIN ì¤í ê³í ë° ì¸ë±ì¤ ê¶ì¥ ì¬íì íì©í ë린 쿼리 ìµì í
- ì¬ë°ë¥¸ ì ê·í(1NF, 2NF, 3NF, BCNF)를 ì ì©í ë°ì´í°ë² ì´ì¤ ì¤í¤ë§ ì¤ê³
- 쿼리 ì±ë¥ì ìí í¨ê³¼ì ì¸ ì¸ë±ì¤ ìì±
- 롤백 ì§ìì í¬í¨í ìì í ë°ì´í°ë² ì´ì¤ ë§ì´ê·¸ë ì´ì ìì±
- SQL ìë¬ ëë²ê¹ ë° ìë¬ ë©ìì§ í´ì
- ì ì í 격리 ìì¤(isolation levels)ì ì ì©í í¸ëìì ì²ë¦¬
- JSON/JSONB ë°ì´í° íì íì©
- í ì¤í¸ë¥¼ ìí ìí ë°ì´í° ìì±
- ë°ì´í°ë² ì´ì¤ ë¤ì´ì¼ë í¸ ê° ë³í (PostgreSQL â MySQL â SQLite)
ì§ìíë ë°ì´í°ë² ì´ì¤ ìì¤í (Supported Database Systems)
PostgreSQL
ì í©í ì¬ë¡: ë³µì¡í 쿼리, JSON ë°ì´í°, ê³ ê¸ ê¸°ë¥, ACID ì¤ì
pip install psycopg2-binary sqlalchemyMySQL/MariaDB
ì í©í ì¬ë¡: ì¹ ì í리ì¼ì´ì , WordPress, ì½ê¸° ë¹ì¤ì´ ëì ìí¬ë¡ë
pip install mysql-connector-python sqlalchemySQLite
ì í©í ì¬ë¡: ë¡ì»¬ ê°ë°, ìë² ëë ë°ì´í°ë² ì´ì¤, í ì¤í¸
pip install sqlite3 # Python ë´ì¥SQL Server
ì í©í ì¬ë¡: ìí°íë¼ì´ì¦ ì í리ì¼ì´ì , Windows íê²½
pip install pyodbc sqlalchemy쿼리 ìì± (Query Writing)
JOINì í¬í¨í 기본 SELECT
-- íí°ë§ì´ í¬í¨ë ë¨ì SELECT
SELECT
column1,
column2,
column3
FROM
table_name
WHERE
condition = 'value'
AND another_condition > 100
ORDER BY
column1 DESC
LIMIT 10;
-- INNER JOIN
SELECT
users.name,
orders.order_date,
orders.total_amount
FROM
users
INNER JOIN
orders ON users.id = orders.user_id
WHERE
orders.status = 'completed';
-- LEFT JOIN (ì£¼ë¬¸ì´ ìë ì¬ì©ì를 í¬í¨íì¬ ëª¨ë ì¡°í)
SELECT
users.name,
COUNT(orders.id) as order_count,
COALESCE(SUM(orders.total_amount), 0) as total_spent
FROM
users
LEFT JOIN
orders ON users.id = orders.user_id
GROUP BY
users.id, users.name;ìë¸ì¿¼ë¦¬ ë° CTE (Common Table Expression)
-- WHERE ì ì ìë¸ì¿¼ë¦¬
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- CTE (ê°ë
ì± í¥ìì ìí´ ê¶ì¥)
WITH high_value_customers AS (
SELECT
user_id,
SUM(total_amount) as lifetime_value
FROM orders
GROUP BY user_id
HAVING SUM(total_amount) > 1000
)
SELECT
users.name,
users.email,
hvc.lifetime_value
FROM users
INNER JOIN high_value_customers hvc ON users.id = hvc.user_id;ìëì° í¨ì (Window Functions)
-- 그룹 ë´ ìì ì§ì
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as salary_rank
FROM
employees;
-- ëê³ (Running totals)
SELECT
order_date,
total_amount,
SUM(total_amount) OVER (ORDER BY order_date) as running_total
FROM
orders;
-- ì´ë íê· (Moving averages)
SELECT
order_date,
total_amount,
AVG(total_amount) OVER (
ORDER BY order_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as moving_avg_7days
FROM
daily_sales;ë ë³µì¡í 쿼리 í¨í´ì examples/complex_queries.sqlì 참조íì¸ì.
쿼리 ìµì í (Query Optimization)
EXPLAIN íì©
-- 쿼리 ì±ë¥ ë¶ì
EXPLAIN ANALYZE
SELECT
users.name,
COUNT(orders.id) as order_count
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name;
-- íì¸í ë´ì©:
-- - Seq Scan (ëì¨) vs Index Scan (ì¢ì)
-- - ëì ë¹ì©(cost) ìì¹
-- - ì²ë¦¬ëë ëëì í ì(row counts)ë¹ ë¥¸ ìµì í í
-- ëì¨: ì¸ë±ì¤ 컬ë¼ì í¨ì ì¬ì©
SELECT * FROM users WHERE LOWER(email) = 'user@example.com';
-- ì¢ì: ì¸ë±ì¤ 컬ë¼ì ê°ê³µíì§ ìì
SELECT * FROM users WHERE email = LOWER('user@example.com');
-- ëì¨: SELECT * ì¬ì©
SELECT * FROM large_table WHERE id = 123;
-- ì¢ì: íìí 컬ë¼ë§ ì í
SELECT id, name, email FROM large_table WHERE id = 123;í¬ê´ì ì¸ ìµì í 기ë²ì references/query-optimization.md를 참조íì¸ì.
ì¤í¤ë§ ì¤ê³ (Schema Design)
ì ê·í ìì¹ (Normalization Principles)
ì 1ì ê·í (1NF): ë°ë³µëë 그룹 ì ê±°, ììì±(atomic) íë³´
-- ì¢ì: 주문 í목ì ìí ë³ë í
ì´ë¸ 구ì±
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_name VARCHAR(100)
);
CREATE TABLE order_items (
order_item_id INT PRIMARY KEY,
order_id INT REFERENCES orders(order_id),
product_name VARCHAR(100)
);ì 2ì ê·í (2NF): 기본í¤ê° ìë 모ë ìì±ì´ ê¸°ë³¸í¤ ì ì²´ì ìì¡´í´ì¼ í¨
-- ì¢ì: ì í ì 보를 ë¶ë¦¬íì¬ ê´ë¦¬
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
product_price DECIMAL(10, 2)
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);ì 3ì ê·í (3NF): ì´íì ì¢ ìì±(transitive dependency)ì´ ìì´ì¼ í¨
ì¼ë°ì ì¸ ì¤í¤ë§ í¨í´
ì¼ëë¤ (One-to-Many):
CREATE TABLE authors (
author_id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100) UNIQUE
);
CREATE TABLE books (
book_id INT PRIMARY KEY,
title VARCHAR(200),
author_id INT NOT NULL,
published_date DATE,
FOREIGN KEY (author_id) REFERENCES authors(author_id)
);ë¤ëë¤ (Many-to-Many):
CREATE TABLE students (
student_id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE courses (
course_id INT PRIMARY KEY,
course_name VARCHAR(100)
);
-- êµì°¨(Junction) í
ì´ë¸
CREATE TABLE enrollments (
enrollment_id INT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT NOT NULL,
enrollment_date DATE,
grade CHAR(2),
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id),
UNIQUE (student_id, course_id)
);ë ë§ì ì¤í¤ë§ í¨í´ì examples/schema_examples.sqlì 참조íì¸ì.
ì¸ë±ì¤ ë° ì±ë¥ (Indexes and Performance)
ì¸ë±ì¤ ìì±
-- ë¨ì¼ ì»¬ë¼ ì¸ë±ì¤
CREATE INDEX idx_users_email ON users(email);
-- ë³µí© ì¸ë±ì¤ (ì»¬ë¼ ììê° ì¤ìí©ëë¤!)
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date);
-- ê³ ì ì¸ë±ì¤
CREATE UNIQUE INDEX idx_users_username ON users(username);
-- ë¶ë¶ ì¸ë±ì¤ (PostgreSQL)
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';ì¸ë±ì¤ ê°ì´ëë¼ì¸
ì¸ë±ì¤ê° íìí ê²½ì°:
- â WHERE ì ì ì¬ì©ëë 컬ë¼
- â JOIN ì¡°ê±´ì ì¬ì©ëë 컬ë¼
- â ORDER BYì ì¬ì©ëë 컬ë¼
- â ì¸ë í¤(Foreign key) 컬ë¼
ì¸ë±ì¤ë¥¼ í¼í´ì¼ íë ê²½ì°:
- â ì주 ìì í ì´ë¸ (< 1000í)
- â ì íë(selectivity)ê° ë®ì ì»¬ë¼ (ì: boolean íë)
- â ì ë°ì´í¸ê° ë§¤ì° ë¹ë²í 컬ë¼
ìì¸í ì¸ë±ì¤ ì ëµì references/indexes-performance.md를 참조íì¸ì.
ë§ì´ê·¸ë ì´ì (Migrations)
ìì í ë§ì´ê·¸ë ì´ì í¨í´
-- 1ë¨ê³: nullable 컬ë¼ì¼ë¡ ì¶ê°
ALTER TABLE users ADD COLUMN status VARCHAR(20);
-- 2ë¨ê³: 기존 ë°ì´í° ì±ì°ê¸°
UPDATE users SET status = 'active' WHERE status IS NULL;
-- 3ë¨ê³: NOT NULL ì ì½ ì¡°ê±´ ë¶ì¬
ALTER TABLE users ALTER COLUMN status SET NOT NULL;
-- 4ë¨ê³: ì íì ìí ê¸°ë³¸ê° ì¤ì
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';
-- 롤백 ê³í
ALTER TABLE users DROP COLUMN status;무ì¤ë¨ ë§ì´ê·¸ë ì´ì (Zero-Downtime Migrations)
-- ì¢ì: 컬ë¼ì 먼ì nullableë¡ ì¶ê°í ë¤ ë°ì´í° ì±ì
ALTER TABLE large_table ADD COLUMN new_column VARCHAR(100);
-- ëë ì
ë°ì´í¸ë ë°°ì¹(batch) ë¨ìë¡ ìí
UPDATE large_table SET new_column = 'value' WHERE new_column IS NULL LIMIT 1000;
-- ìë£ë ëê¹ì§ ë°ë³µ
-- ê·¸ ë¤ì NOT NULL ì¤ì
ALTER TABLE large_table ALTER COLUMN new_column SET NOT NULL;ì¶ê° ë§ì´ê·¸ë ì´ì
í¨í´ì examples/migrations.sqlì 참조íì¸ì.
ê³ ê¸ í¨í´ (Advanced Patterns)
UPSERT (Insert or Update)
-- PostgreSQL
INSERT INTO users (user_id, name, email, updated_at)
VALUES (1, 'John Doe', 'john@example.com', NOW())
ON CONFLICT (user_id)
DO UPDATE SET
name = EXCLUDED.name,
email = EXCLUDED.email,
updated_at = NOW();
-- MySQL
INSERT INTO users (user_id, name, email, updated_at)
VALUES (1, 'John Doe', 'john@example.com', NOW())
ON DUPLICATE KEY UPDATE
name = VALUES(name),
email = VALUES(email),
updated_at = NOW();ì¬ê· CTE (Recursive CTEs)
-- ê³ì¸µí ë°ì´í° íì
WITH RECURSIVE employee_hierarchy AS (
-- Anchor: ìµìì ì§ì
SELECT id, name, manager_id, 1 as level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Recursive: ì´ì ë 벨ì ë³´ê³ íë ì§ìë¤
SELECT e.id, e.name, e.manager_id, eh.level + 1
FROM employees e
INNER JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM employee_hierarchy ORDER BY level, name;í¼ë² í
ì´ë¸, JSON ìì
ë° ë²í¬ ìì
ì í¬í¨í ê³ ê¸ í¨í´ì references/advanced-patterns.md를 참조íì¸ì.
ëª¨ë² ì¬ë¡ (Best Practices)
íµì¬ ìì¹
- **íì íë¼ë¯¸í°íë 쿼리를 ì¬ì©**íì¬ SQL ì¸ì ì ì ë°©ì§íì¸ì.
- **ì°ê´ë ìì ì í¸ëìì ì ì¬ì©**íì¬ ììì±ì ë³´ì¥íì¸ì.
- ì ì í ì ì½ ì¡°ê±´ì ì¶ê°íì¸ì (PRIMARY KEY, FOREIGN KEY, NOT NULL, CHECK).
- íìì¤í¬í ì»¬ë¼ (created_at, updated_at)ì í ì´ë¸ì í¬í¨íì¸ì.
- í ì´ë¸ê³¼ 컬ë¼ì ì미 ìë ì´ë¦ì ì§ì¼ì¸ì.
- SELECT * ì ì§ìíê³ íìí 컬ë¼ë§ ëª ìíì¸ì.
- ì¡°ì¸ ì±ë¥ì ìí´ **ì¸ë í¤ì ì¸ë±ì¤**를 ê³ ë ¤íì¸ì.
- ê°ë³ ê¸¸ì´ ë¬¸ìì´ìë CHAR ëì VARCHAR를 ì¬ì©íì¸ì.
- IS NULL / IS NOT NULLì ì¬ì©íì¬ **NULL ê°ì ì ì í ì²ë¦¬**íì¸ì.
- ì ì í ë°ì´í° íì ì ì¬ì©íì¸ì (ê¸ì¡ì FLOATê° ìë DECIMAL ê¶ì¥).
ëª¨ë² ì¬ë¡ ì ì© ìì:
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT NOT NULL,
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
total_amount DECIMAL(10, 2) CHECK (total_amount >= 0),
status VARCHAR(20) CHECK (status IN ('pending', 'completed', 'cancelled')),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);í¬ê´ì ì¸ ëª¨ë² ì¬ë¡ë references/best-practices.md를 참조íì¸ì.
ì주 ë°ìíë 문ì (Common Pitfalls)
ë¤ìì 주ìíì¸ì:
- N+1 쿼리 문ì - ì¿¼ë¦¬ê° ë°ë³µë¬¸ ììì ì¤íëì§ ìëë¡ JOINì íì©íì¸ì.
- LIMIT ë¶ì¬ - í° í ì´ë¸ íì ì LIMITì ìì§ ë§ì¸ì.
- ììì íì ë³í - íì ë¶ì¼ì¹ë ì¸ë±ì¤ ì¬ì©ì ë°©í´í ì ììµëë¤.
- EXISTS ëì COUNT(*) ì¬ì© - ì¡´ì¬ ì¬ë¶ë§ íì¸í ëë EXISTSê° í¨ì¨ì ì ëë¤.
- NULL ì²ë¦¬ ì¤ì (NULL = NULLì TRUEê° ìëë¼ NULLì ëë¤).
- ììë°©í¸ì¼ë¡ SELECT DISTINCT ì¬ì© - 쿼리ì 근본ì ì¸ ìì¸ì ê³ ì¹ë ëì DISTINCTë¡ ì¤ë³µë§ ê°ë¦¬ë ê²ì ìíí©ëë¤.
- ì°ê´ ìì ììì í¸ëìì ëë½.
- ì¸ë±ì¤ ì»¬ë¼ ê°ê³µ - ì¸ë±ì¤ 컬ë¼ì í¨ì를 ìì°ë©´ ì¸ë±ì¤ë¥¼ í ì ììµëë¤.
ìì - N+1 문ì í¼í기:
# ëì¨: N+1 쿼리
users = db.query("SELECT * FROM users")
for user in users:
orders = db.query("SELECT * FROM orders WHERE user_id = ?", user.id)
# ì¢ì: JOINì ì´ì©í ë¨ì¼ 쿼리
result = db.query("""
SELECT users.*, orders.*
FROM users
LEFT JOIN orders ON users.id = orders.user_id
""")문ì í´ê²° ë°©ë²ì ì ì²´ 목ë¡ì references/common-pitfalls.md를 참조íì¸ì.
í¬í¼ ì¤í¬ë¦½í¸ ë° ìì
íì© ê°ë¥ 리ìì¤
í¬í¼ ì¤í¬ë¦½í¸ (scripts/):
sql_helper.py- 쿼리 ë¹ë©, ì¤í¤ë§ ë´ì± ì¡°ì¬(introspection), ì¸ë±ì¤ ë¶ì ë° ë§ì´ê·¸ë ì´ì ë³´ì¡° ì í¸ë¦¬í°
ìì (examples/):
complex_queries.sql- CTE, ìëì° í¨ì, ìë¸ì¿¼ë¦¬ë¥¼ íì©í ê³ ê¸ ì¿¼ë¦¬ í¨í´schema_examples.sql- ë¤ìí ì ì¤ì¼ì´ì¤ë¥¼ ìí ì ì²´ ì¤í¤ë§ ì¤ê³ ììmigrations.sql- ìì í ë§ì´ê·¸ë ì´ì í¨í´ ë° ë¬´ì¤ë¨ 기ë²
참조 문ì (references/):
query-optimization.md- í¬ê´ì ì¸ ì¿¼ë¦¬ ìµì í ê¸°ë² ë° EXPLAIN ë¶ìindexes-performance.md- ìì¸ ì¸ë±ì¤ ì ëµ, ì ì§ë³´ì ë° ëª¨ëí°ë§advanced-patterns.md- UPSERT, ë²í¬ ìì , í¼ë² í ì´ë¸, JSON ìì , ì¬ê· 쿼리best-practices.md- ì ì²´ SQL ëª¨ë² ì¬ë¡ ê°ì´ëcommon-pitfalls.md- ì¼ë°ì ì¸ ì¤ìì ë°©ì§ ë°©ë²
ë¹ ë¥¸ ìì ê°ì´ë
- 기본 쿼리ë ììì ë³´ì¬ì¤ í¨í´ì ì¬ì©íì¸ì.
- ìµì íê° íìí ê²½ì° EXPLAINì¼ë¡ ììíê³
references/query-optimization.md를 íì¸íì¸ì. - ì¤í¤ë§ ì¤ê³ ì ì ê·í í¨í´ì ê²í íê³
examples/schema_examples.sqlì 참조íì¸ì. - ë³µì¡í ìë리ì¤ë
references/advanced-patterns.md를 íì¸íì¸ì. - ì í¸ë¦¬í°ê° íìí ê²½ì°
scripts/sql_helper.py를 íì©íì¸ì.
ìí¬íë¡ì° (Workflow)
SQL ë°ì´í°ë² ì´ì¤ ìì ì:
- ì구 ì¬í íì - ì´ë¤ ë°ì´í°ë¥¼ ì¡°ííê±°ë ì ì¥í´ì¼ íëê°?
- ì¤í¤ë§ ì¤ê³ - ì ê·í ì ì© ë° ì ì í ë°ì´í° íì ì í
- ì¸ë±ì¤ ìì± - ì¸ë í¤ ë° ì주 ì¡°íëë ì»¬ë¼ ì¸ë±ì±
- 쿼리 ìì± - ë¨ìíê² ììíì¬ íìì ë°ë¼ ë³µì¡ë ì¶ê°
- ìµì í - EXPLAINì ì¬ì©íì¬ ë³ëª© ì§ì ìë³
- í ì¤í¸ - ìí ë°ì´í° ë° ìì§ ì¼ì´ì¤(edge cases) ê²ì¦
- 문ìí - ë³µì¡í 쿼리ìë 주ì ì¶ê°
ë§ì´ê·¸ë ì´ì ì:
- ë³ê²½ ê³í ì립 - ìí¥ì ë°ë í ì´ë¸ê³¼ ìì¡´ì± ìë³
- ë§ì´ê·¸ë ì´ì ìì± - Up/Down ë§ì´ê·¸ë ì´ì 모ë ìì±
- ë³µì¬ë³¸ í ì¤í¸ - ê°ë°ì© ë°ì´í°ë² ì´ì¤ìì 먼ì í ì¤í¸
- ë°±ì - íì ë§ì´ê·¸ë ì´ì ì¤í ì ë°±ì ìí
- ì¤í - í¸ëí½ì´ ì ì ìê°ì ì¤í
- ê²ì¦ - ì¤í í ë°ì´í° ë¬´ê²°ì± íì¸