SQL 기본


SQL이란?

**SQL (Structured Query Language)**은 관계형 데이터베이스 관리 시스템(RDBMS)에서 데이터를 정의, 조작, 제어하기 위해 사용하는 표준화된 언어입니다. 데이터베이스와 상호작용하는 가장 기본적이고 중요한 도구입니다.


SQL의 분류

1. DDL (Data Definition Language) - 데이터 정의어

데이터베이스 구조를 정의하는 명령어

  • CREATE: 데이터베이스, 테이블, 인덱스 등 생성
  • ALTER: 기존 구조 수정
  • DROP: 데이터베이스, 테이블 등 삭제
  • TRUNCATE: 테이블 데이터 전체 삭제

2. DML (Data Manipulation Language) - 데이터 조작어

데이터를 조작하는 명령어

  • SELECT: 데이터 조회
  • INSERT: 데이터 삽입
  • UPDATE: 데이터 수정
  • DELETE: 데이터 삭제

3. DCL (Data Control Language) - 데이터 제어어

권한을 제어하는 명령어

  • GRANT: 권한 부여
  • REVOKE: 권한 회수

4. TCL (Transaction Control Language) - 트랜잭션 제어어

트랜잭션을 제어하는 명령어

  • COMMIT: 트랜잭션 확정
  • ROLLBACK: 트랜잭션 취소
  • SAVEPOINT: 저장점 설정

DDL - 데이터 정의어

데이터베이스 생성 및 삭제

-- 데이터베이스 생성
CREATE DATABASE mydb;
 
-- 데이터베이스 사용
USE mydb;  -- MySQL
\c mydb;   -- PostgreSQL
 
-- 데이터베이스 삭제
DROP DATABASE mydb;

테이블 생성

-- 기본 테이블 생성
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE,
    age INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- AUTO_INCREMENT (MySQL)
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);
 
-- SERIAL (PostgreSQL)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

테이블 수정

-- 컬럼 추가
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
 
-- 컬럼 수정
ALTER TABLE users MODIFY COLUMN email VARCHAR(200);  -- MySQL
ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(200);  -- PostgreSQL
 
-- 컬럼 삭제
ALTER TABLE users DROP COLUMN phone;
 
-- 컬럼 이름 변경
ALTER TABLE users RENAME COLUMN email TO email_address;

테이블 삭제

-- 테이블 삭제
DROP TABLE users;
 
-- 테이블 데이터만 삭제 (구조 유지)
TRUNCATE TABLE users;

인덱스 생성

-- 인덱스 생성
CREATE INDEX idx_email ON users(email);
 
-- 유니크 인덱스
CREATE UNIQUE INDEX idx_unique_email ON users(email);
 
-- 복합 인덱스
CREATE INDEX idx_name_email ON users(username, email);
 
-- 인덱스 삭제
DROP INDEX idx_email ON users;  -- MySQL
DROP INDEX idx_email;  -- PostgreSQL

DML - 데이터 조작어

SELECT - 데이터 조회

-- 모든 컬럼 조회
SELECT * FROM users;
 
-- 특정 컬럼 조회
SELECT id, username, email FROM users;
 
-- 조건부 조회
SELECT * FROM users WHERE age > 18;
 
-- 여러 조건
SELECT * FROM users 
WHERE age > 18 AND email LIKE '%@gmail.com';
 
-- 정렬
SELECT * FROM users ORDER BY created_at DESC;
 
-- 제한
SELECT * FROM users LIMIT 10;  -- MySQL
SELECT * FROM users LIMIT 10 OFFSET 20;  -- MySQL
SELECT * FROM users FETCH FIRST 10 ROWS ONLY;  -- PostgreSQL
 
-- 그룹화
SELECT department, COUNT(*) 
FROM employees 
GROUP BY department;
 
-- 집계 함수
SELECT 
    department,
    COUNT(*) as count,
    AVG(salary) as avg_salary,
    MAX(salary) as max_salary,
    MIN(salary) as min_salary
FROM employees
GROUP BY department;

INSERT - 데이터 삽입

-- 단일 행 삽입
INSERT INTO users (username, email, age) 
VALUES ('john', 'john@example.com', 25);
 
-- 여러 행 삽입
INSERT INTO users (username, email, age) VALUES
('jane', 'jane@example.com', 30),
('bob', 'bob@example.com', 28);
 
-- 다른 테이블에서 데이터 삽입
INSERT INTO users (username, email)
SELECT name, email FROM temp_users;
 
-- 삽입된 데이터 반환 (PostgreSQL)
INSERT INTO users (username, email) 
VALUES ('john', 'john@example.com')
RETURNING *;

UPDATE - 데이터 수정

-- 단일 행 수정
UPDATE users 
SET email = 'newemail@example.com' 
WHERE id = 1;
 
-- 여러 컬럼 수정
UPDATE users 
SET email = 'newemail@example.com', age = 26 
WHERE id = 1;
 
-- 조건부 수정
UPDATE users 
SET age = age + 1 
WHERE created_at < '2023-01-01';
 
-- 수정된 데이터 반환 (PostgreSQL)
UPDATE users 
SET email = 'newemail@example.com' 
WHERE id = 1
RETURNING *;

DELETE - 데이터 삭제

-- 조건부 삭제
DELETE FROM users WHERE id = 1;
 
-- 여러 조건
DELETE FROM users 
WHERE age < 18 AND created_at < '2023-01-01';
 
-- 모든 데이터 삭제 (주의!)
DELETE FROM users;
 
-- 삭제된 데이터 반환 (PostgreSQL)
DELETE FROM users WHERE id = 1
RETURNING *;

JOIN - 테이블 조인

INNER JOIN

-- 내부 조인
SELECT u.username, o.order_id, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
 
-- 동일한 결과 (WHERE 사용)
SELECT u.username, o.order_id, o.total
FROM users u, orders o
WHERE u.id = o.user_id;

LEFT JOIN (LEFT OUTER JOIN)

-- 왼쪽 외부 조인
SELECT u.username, o.order_id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

RIGHT JOIN (RIGHT OUTER JOIN)

-- 오른쪽 외부 조인
SELECT u.username, o.order_id, o.total
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

FULL OUTER JOIN

-- 완전 외부 조인 (PostgreSQL)
SELECT u.username, o.order_id, o.total
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

CROSS JOIN

-- 교차 조인 (카테시안 곱)
SELECT u.username, p.product_name
FROM users u
CROSS JOIN products p;

SELF JOIN

-- 자기 조인
SELECT e1.name as employee, e2.name as manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.id;

서브쿼리 (Subquery)

스칼라 서브쿼리

-- 단일 값 반환
SELECT 
    username,
    (SELECT COUNT(*) FROM orders WHERE user_id = users.id) as order_count
FROM users;

인라인 뷰

-- FROM 절의 서브쿼리
SELECT * FROM (
    SELECT department, AVG(salary) as avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg
WHERE avg_salary > 50000;

상관 서브쿼리

-- 외부 쿼리와 연관된 서브쿼리
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.user_id = u.id
);

IN 서브쿼리

-- IN 연산자 사용
SELECT * FROM users
WHERE id IN (
    SELECT user_id FROM orders 
    WHERE total > 1000
);

집계 함수 및 그룹화

집계 함수

-- COUNT: 행 개수
SELECT COUNT(*) FROM users;
SELECT COUNT(DISTINCT email) FROM users;
 
-- SUM: 합계
SELECT SUM(total) FROM orders;
 
-- AVG: 평균
SELECT AVG(age) FROM users;
 
-- MAX/MIN: 최대/최소
SELECT MAX(created_at), MIN(created_at) FROM users;
 
-- GROUP_CONCAT (MySQL): 그룹 연결
SELECT department, GROUP_CONCAT(name) 
FROM employees 
GROUP BY department;

GROUP BY

-- 기본 그룹화
SELECT department, COUNT(*) 
FROM employees 
GROUP BY department;
 
-- 여러 컬럼 그룹화
SELECT department, position, COUNT(*) 
FROM employees 
GROUP BY department, position;
 
-- HAVING: 그룹 조건
SELECT department, AVG(salary) as avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 50000;

윈도우 함수 (PostgreSQL, MySQL 8.0+)

-- RANK: 순위
SELECT 
    name,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank
FROM employees;
 
-- ROW_NUMBER: 행 번호
SELECT 
    name,
    ROW_NUMBER() OVER (ORDER BY created_at) as row_num
FROM users;
 
-- LAG/LEAD: 이전/다음 행 값
SELECT 
    date,
    sales,
    LAG(sales) OVER (ORDER BY date) as prev_sales,
    LEAD(sales) OVER (ORDER BY date) as next_sales
FROM daily_sales;

제약 조건 (Constraints)

PRIMARY KEY

-- 기본키
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50)
);
 
-- 복합 기본키
CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    quantity INT,
    PRIMARY KEY (order_id, product_id)
);

FOREIGN KEY

-- 외래키
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id)
);
 
-- 외래키 옵션
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE      -- 부모 삭제 시 자식도 삭제
    ON UPDATE CASCADE      -- 부모 업데이트 시 자식도 업데이트
);

UNIQUE

-- 유니크 제약
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE
);

CHECK

-- 체크 제약
CREATE TABLE products (
    id INT PRIMARY KEY,
    price DECIMAL(10,2) CHECK (price > 0),
    age INT CHECK (age >= 0 AND age <= 120)
);

NOT NULL

-- NOT NULL 제약
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL
);

DEFAULT

-- 기본값
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status VARCHAR(20) DEFAULT 'active'
);

뷰 (View)

뷰 생성

-- 기본 뷰
CREATE VIEW user_orders AS
SELECT 
    u.username,
    o.order_id,
    o.total,
    o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id;
 
-- 뷰 조회
SELECT * FROM user_orders;
 
-- 뷰 삭제
DROP VIEW user_orders;

뷰 수정

-- 뷰 수정 (PostgreSQL)
CREATE OR REPLACE VIEW user_orders AS
SELECT 
    u.username,
    o.order_id,
    o.total
FROM users u
JOIN orders o ON u.id = o.user_id;

트랜잭션

트랜잭션 기본

-- 트랜잭션 시작
BEGIN;  -- PostgreSQL
START TRANSACTION;  -- MySQL
 
-- 작업 수행
INSERT INTO orders (user_id, total) VALUES (1, 100);
UPDATE users SET balance = balance - 100 WHERE id = 1;
 
-- 커밋
COMMIT;
 
-- 롤백
ROLLBACK;

저장점 (Savepoint)

BEGIN;
INSERT INTO orders (user_id, total) VALUES (1, 100);
SAVEPOINT sp1;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 문제 발생 시
ROLLBACK TO SAVEPOINT sp1;
COMMIT;

SQL 함수

문자열 함수

-- CONCAT: 문자열 연결
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;  -- MySQL
SELECT first_name || ' ' || last_name AS full_name FROM users;  -- PostgreSQL
 
-- UPPER/LOWER: 대소문자 변환
SELECT UPPER(username), LOWER(email) FROM users;
 
-- SUBSTRING: 문자열 자르기
SELECT SUBSTRING(email, 1, 5) FROM users;
 
-- LENGTH: 문자열 길이
SELECT LENGTH(username) FROM users;
 
-- TRIM: 공백 제거
SELECT TRIM('  hello  ') FROM dual;

숫자 함수

-- ROUND: 반올림
SELECT ROUND(price, 2) FROM products;
 
-- FLOOR/CEIL: 내림/올림
SELECT FLOOR(price), CEIL(price) FROM products;
 
-- ABS: 절댓값
SELECT ABS(-10) FROM dual;
 
-- MOD: 나머지
SELECT MOD(10, 3) FROM dual;

날짜 함수

-- 현재 날짜/시간
SELECT NOW(), CURRENT_DATE, CURRENT_TIME;
 
-- 날짜 계산
SELECT DATE_ADD(created_at, INTERVAL 1 DAY) FROM users;  -- MySQL
SELECT created_at + INTERVAL '1 day' FROM users;  -- PostgreSQL
 
-- 날짜 추출
SELECT YEAR(created_at), MONTH(created_at) FROM users;  -- MySQL
SELECT EXTRACT(YEAR FROM created_at) FROM users;  -- PostgreSQL
 
-- 날짜 포맷
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM users;  -- MySQL
SELECT TO_CHAR(created_at, 'YYYY-MM-DD') FROM users;  -- PostgreSQL

SQL 모범 사례

1. 쿼리 최적화

  • SELECT * 사용 지양
  • 적절한 WHERE 조건 사용
  • 인덱스 활용
  • 불필요한 JOIN 제거

2. 보안

  • SQL 인젝션 방지 (Prepared Statement 사용)
  • 최소 권한 원칙
  • 입력 데이터 검증

3. 가독성

  • 명확한 별칭 사용
  • 적절한 들여쓰기
  • 주석 활용
  • 일관된 네이밍

4. 성능

  • 인덱스 활용
  • EXPLAIN으로 실행 계획 확인
  • 서브쿼리 최적화
  • 배치 작업 활용

참고

  • SQL은 데이터베이스와 상호작용하는 기본 언어입니다
  • 표준 SQL을 학습하되, 각 DBMS의 특성을 이해하세요
  • 쿼리 최적화를 통해 성능을 향상시키세요
  • 보안을 위해 Prepared Statement를 사용하세요
  • 정기적으로 쿼리 성능을 모니터링하세요