테이블 생성과 데이터 타입
테이블을 만드는 것은 데이터베이스의 뼈대를 세우는 작업입니다. 구조를 잘 설계해야 나중에 고통받지 않습니다. DDL(Data Definition Language)은 데이터의 구조를 정의하는 SQL이며, CREATE, ALTER, DROP, TRUNCATE 등이 여기에 해당합니다.
SQL은 크게 네 가지 범주로 나뉩니다. DDL 외에 DML(데이터 조작), DCL(권한 제어), TCL(트랜잭션 제어)이 있으며, DDL은 테이블의 구조 자체를 다루는 명령입니다.
┌──────────────────────────────────────────────────────┐
│ DDL (Data Definition Language) — 구조 정의 │
│ CREATE: 테이블, 인덱스, 뷰 등 생성 │
│ ALTER: 구조 변경 (컬럼 추가/삭제/수정) │
│ DROP: 객체 삭제 │
│ TRUNCATE: 데이터 전체 삭제 (구조 유지) │
├──────────────────────────────────────────────────────┤
│ DML (Data Manipulation Language) — 데이터 조작 │
│ SELECT, INSERT, UPDATE, DELETE │
├──────────────────────────────────────────────────────┤
│ DCL (Data Control Language) — 권한 제어 │
│ GRANT, REVOKE │
├──────────────────────────────────────────────────────┤
│ TCL (Transaction Control Language) — 트랜잭션 제어 │
│ COMMIT, ROLLBACK, SAVEPOINT │
└──────────────────────────────────────────────────────┘
DDL은 자동 커밋(Auto Commit)됩니다.
CREATE TABLE을 실행하면 즉시 반영되며 ROLLBACK할 수 없습니다.CREATE TABLE
CREATE TABLE employees (
id NUMBER PRIMARY KEY,
name VARCHAR2(100) NOT NULL,
email VARCHAR2(200) UNIQUE,
department VARCHAR2(50) DEFAULT '미배정',
salary NUMBER(10,2),
hire_date DATE DEFAULT SYSDATE
);각 컬럼에 데이터 타입과 제약 조건을 지정합니다. 타입은 저장할 수 있는 데이터의 종류와 크기를 결정하고, 제약 조건은 허용되는 값의 범위를 제한합니다.
CREATE TABLE 문법 상세
CREATE TABLE [스키마.]테이블명 (
컬럼명 데이터타입 [DEFAULT 기본값] [제약조건],
컬럼명 데이터타입 [DEFAULT 기본값] [제약조건],
...
[테이블 수준 제약조건]
);1. 영문, 숫자, 밑줄(_)만 사용 (한글 가능하지만 비권장)
2. 첫 글자는 영문이어야 함
3. 예약어(SELECT, TABLE 등) 사용 불가
4. 같은 스키마 내에서 테이블명 중복 불가
5. 같은 테이블 내에서 컬럼명 중복 불가
6. 대소문자 구분은 DBMS에 따라 다름
Oracle: 대문자로 저장 (큰따옴표 사용 시 원본 유지)
MySQL: OS에 따라 다름 (Linux: 구분, Windows: 비구분)
PostgreSQL: 소문자로 저장 (큰따옴표 사용 시 원본 유지)네이밍 컨벤션
테이블명
* 단수형 사용: user (O), users (△)
* snake_case: order_item (O), OrderItem (X)
* 접두어 사용 여부는 팀 규칙에 따름: tbl_user, t_user
컬럼명
* snake_case: first_name (O), firstName (X)
* 약어 일관성: dept_id, emp_id (전체 프로젝트 통일)
* 예약어 피하기: order → order_seq, status → order_status
기본키 컬럼
* 테이블명_id: user_id, product_id (권장)
* 또는 단순 id (간단하지만 조인 시 혼란 가능)
외래키 컬럼
* 참조 테이블의 기본키와 동일한 이름 사용
* orders.user_id → users.user_id (일관성)주요 데이터 타입
데이터 타입은 컬럼에 저장할 수 있는 데이터의 종류, 크기, 형식을 결정합니다. 적절한 타입을 선택하면 저장 공간을 절약하고, 데이터 무결성을 보장하며, 쿼리 성능도 향상됩니다.
숫자 타입
숫자 타입은 정수와 실수를 구분하며, 특히 금액 처리에서 정확한 타입 선택이 중요합니다.
| DBMS | 정수 | 실수 | 고정 소수점 |
|---|---|---|---|
| Oracle | NUMBER, NUMBER(10) | NUMBER(10,2) | NUMBER(p,s) |
| MySQL | TINYINT, INT, BIGINT | FLOAT, DOUBLE | DECIMAL(p,s) |
| PostgreSQL | SMALLINT, INTEGER, BIGINT | REAL, DOUBLE PRECISION | NUMERIC(p,s) |
INT vs BIGINT
INT: -21억 ~ 21억 (4바이트)
BIGINT: -922경 ~ 922경 (8바이트)
→ 일반적으로 INT 충분, 대규모 시퀀스/ID는 BIGINT
FLOAT vs DECIMAL
FLOAT: 근사값 저장 (0.1 + 0.2 ≠ 0.3!)
DECIMAL: 정확한 값 저장 (0.1 + 0.2 = 0.3)
→ 금액은 반드시 DECIMAL 사용
NUMBER(p, s)의 의미 (Oracle)
p (precision): 전체 유효 숫자 개수
s (scale): 소수점 이하 자릿수
NUMBER(5,2): 최대 999.99
NUMBER(10,0) = NUMBER(10): 10자리 정수문자열 타입
문자열은 가장 자주 사용되는 데이터 타입입니다. 고정 길이와 가변 길이의 차이를 정확히 이해해야 합니다.
| DBMS | 고정 길이 | 가변 길이 | 대용량 텍스트 |
|---|---|---|---|
| Oracle | CHAR(n) | VARCHAR2(n) | CLOB |
| MySQL | CHAR(n) | VARCHAR(n) | TEXT, LONGTEXT |
| PostgreSQL | CHAR(n) | VARCHAR(n) | TEXT |
CHAR(10)에 'abc' 저장: 'abc ' (7바이트 공백 패딩)
VARCHAR(10)에 'abc' 저장: 'abc' (3바이트만 사용)
CHAR가 적합한 경우:
* 고정 길이 코드: 우편번호(5자리), 국가코드(2자리)
* 성별(M/F), Y/N 플래그
* 길이가 항상 같은 해시값
VARCHAR가 적합한 경우:
* 이름, 이메일, 주소 등 가변 길이 데이터
* 대부분의 문자열 데이터
VARCHAR 최대 길이:
Oracle VARCHAR2: 4000바이트 (확장 시 32767)
MySQL VARCHAR: 65535바이트 (행 전체 크기 제한)
PostgreSQL VARCHAR: 약 1GB (실질적으로 무제한)날짜/시간 타입
날짜/시간 타입은 DBMS마다 가장 차이가 큰 영역이므로, 사용하는 DBMS의 특성을 정확히 파악해야 합니다.
| DBMS | 날짜만 | 날짜+시간 | 타임존 포함 |
|---|---|---|---|
| Oracle | DATE (시간 포함) | TIMESTAMP | TIMESTAMP WITH TIME ZONE |
| MySQL | DATE | DATETIME, TIMESTAMP | - |
| PostgreSQL | DATE | TIMESTAMP | TIMESTAMPTZ |
Oracle DATE: '2024-06-15 14:30:00' (초 단위까지)
Oracle TIMESTAMP: '2024-06-15 14:30:00.123456' (마이크로초)
MySQL DATE: '2024-06-15' (날짜만)
MySQL DATETIME: '2024-06-15 14:30:00' (시간 포함)
MySQL TIMESTAMP: '2024-06-15 14:30:00' (UTC 변환 저장)
주의: MySQL TIMESTAMP는 타임존 변환이 자동으로 일어남
저장 시: 서버 타임존 → UTC로 변환
조회 시: UTC → 서버 타임존으로 변환
→ 글로벌 서비스에서는 TIMESTAMP 권장불리언 타입
불리언은 참/거짓 두 가지 값만 저장하는 타입이지만, DBMS마다 지원 방식이 다릅니다.
Oracle:
* BOOLEAN 타입 없음 (PL/SQL에서만 사용 가능)
* NUMBER(1) 또는 CHAR(1)로 대체
* 0/1 또는 'Y'/'N' 관례
MySQL:
* BOOLEAN = TINYINT(1) (내부적으로 0과 1)
* TRUE = 1, FALSE = 0
PostgreSQL:
* 네이티브 BOOLEAN 타입 지원
* TRUE, FALSE, NULLALTER TABLE
테이블 구조를 변경하는 명령입니다. 운영 중인 테이블의 구조를 변경할 때는 서비스 영향을 반드시 고려해야 합니다.
-- 컬럼 추가
ALTER TABLE employees ADD phone VARCHAR2(20);
ALTER TABLE employees ADD (phone VARCHAR2(20), address VARCHAR2(300));
-- 컬럼 수정 (타입, 크기 변경)
ALTER TABLE employees MODIFY name VARCHAR2(200);
-- 컬럼 삭제
ALTER TABLE employees DROP COLUMN phone;
-- 컬럼 이름 변경
ALTER TABLE employees RENAME COLUMN name TO full_name; -- Oracle
ALTER TABLE employees CHANGE name full_name VARCHAR(200); -- MySQL
-- 테이블 이름 변경
ALTER TABLE employees RENAME TO staff; -- Oracle
RENAME TABLE employees TO staff; -- MySQL
-- 제약 조건 추가
ALTER TABLE employees ADD CONSTRAINT emp_dept_fk
FOREIGN KEY (dept_id) REFERENCES departments(dept_id);
-- 제약 조건 삭제
ALTER TABLE employees DROP CONSTRAINT emp_dept_fk;ALTER TABLE 주의사항
운영 중인 테이블의 ALTER는 서비스에 직접적인 영향을 줄 수 있습니다. 특히 대용량 테이블에서는 신중하게 실행해야 합니다.
1. 데이터가 있는 테이블의 타입 변경
기존 데이터가 새 타입과 호환되어야 함
VARCHAR(100) → VARCHAR(50): 100자 데이터가 있으면 실패
VARCHAR → NUMBER: 문자 데이터가 있으면 실패
2. NOT NULL 추가
기존 행에 NULL이 있으면 실패
→ 먼저 UPDATE로 NULL을 채운 후 NOT NULL 추가
3. 운영 중 ALTER TABLE
대용량 테이블에서 ALTER는 오래 걸릴 수 있음
Oracle/MySQL: 테이블 잠금이 발생할 수 있음
PostgreSQL: 일부 ALTER는 잠금 없이 수행 가능
→ 운영 시간에는 피하고, 유지보수 시간에 수행DROP TABLE과 TRUNCATE
테이블이나 데이터를 삭제하는 DDL 명령입니다. 두 명령 모두 자동 커밋되므로 ROLLBACK이 불가능합니다. 운영 환경에서 실행할 때는 각별한 주의가 필요합니다.
-- 테이블 삭제 (구조 + 데이터 모두 삭제)
DROP TABLE employees;
-- Oracle: 휴지통에 보관 (복구 가능)
DROP TABLE employees; -- 휴지통으로
FLASHBACK TABLE employees TO BEFORE DROP; -- 복구
DROP TABLE employees PURGE; -- 완전 삭제
-- TRUNCATE: 데이터만 삭제 (구조는 유지)
TRUNCATE TABLE employees;┌─────────┬───────────────┬───────────────┬────────────┐
│ │ DELETE │ TRUNCATE │ DROP │
├─────────┼───────────────┼───────────────┼────────────┤
│삭제 대상│ 조건에 맞는 행│ 모든 행 │ 테이블 자체│
│구조 유지│ ✓ │ ✓ │ ✗ │
│ WHERE │ 사용 가능 │ 사용 불가 │ - │
│ ROLLBACK│ 가능 │ 불가 (DDL) │ 불가 (DDL) │
│ 속도 │ 느림 (행 단위)│ 빠름 (즉시) │ 빠름 │
│ 로그 │ 행마다 기록 │ 최소 로그 │ 최소 로그 │
│ 트리거 │ 실행됨 │ 실행 안 됨 │ - │
│ Auto Inc│ 유지 │ 초기화 │ - │
└─────────┴───────────────┴───────────────┴────────────┘
TRUNCATE는 DDL이므로 자동 커밋됩니다!
실수로 TRUNCATE하면 복구가 매우 어렵습니다.임시 테이블
임시 테이블은 세션이나 트랜잭션 동안만 존재하는 테이블입니다. 복잡한 쿼리의 중간 결과를 저장하거나, ETL 작업의 변환 단계에서 활용됩니다. 다른 세션에서는 같은 이름의 임시 테이블이라도 서로의 데이터를 볼 수 없습니다.
-- Oracle: 글로벌 임시 테이블
CREATE GLOBAL TEMPORARY TABLE temp_results (
id NUMBER,
value VARCHAR2(100)
) ON COMMIT DELETE ROWS; -- 커밋 시 데이터 삭제
-- ON COMMIT PRESERVE ROWS: 세션 종료 시 삭제
-- MySQL: 세션 임시 테이블
CREATE TEMPORARY TABLE temp_results (
id INT,
value VARCHAR(100)
);
-- 세션 종료 시 자동 삭제
-- PostgreSQL: 세션 임시 테이블
CREATE TEMPORARY TABLE temp_results (
id INTEGER,
value VARCHAR(100)
);1. 복잡한 쿼리의 중간 결과 저장
2. ETL 작업 시 데이터 변환 단계
3. 보고서 생성을 위한 집계 데이터 임시 저장
4. 대량 데이터 처리 시 성능 최적화CTAS (CREATE TABLE AS SELECT)
기존 테이블의 데이터를 복사하여 새 테이블을 생성합니다. 백업, 데이터 이관, 테스트 환경 구성 등에 유용합니다.
-- 구조와 데이터 복사
CREATE TABLE employees_backup AS
SELECT * FROM employees;
-- 구조만 복사 (데이터 없이)
CREATE TABLE employees_template AS
SELECT * FROM employees WHERE 1 = 0;
-- 특정 조건의 데이터만 복사
CREATE TABLE senior_employees AS
SELECT id, name, salary, hire_date
FROM employees
WHERE hire_date < '2020-01-01';복사되는 것: 컬럼명, 데이터 타입, 데이터
복사되지 않는 것: 기본키, 외래키, 인덱스, 트리거, 기본값
NOT NULL 제약은 복사되지만, 명시적 이름이 있는 제약은 복사되지 않음
→ 제약 조건은 ALTER TABLE로 별도 추가해야 합니다.
→ 인덱스가 필요하면 CREATE INDEX로 별도 생성합니다.시퀀스와 자동 증가
기본키에 고유한 번호를 자동으로 부여하는 방법입니다. 수동으로 번호를 관리하면 동시 삽입 시 중복이 발생할 수 있으므로, DBMS가 제공하는 자동 증가 메커니즘을 사용하는 것이 안전합니다.
-- Oracle: 시퀀스
CREATE SEQUENCE emp_seq START WITH 1 INCREMENT BY 1;
INSERT INTO employees (id, name) VALUES (emp_seq.NEXTVAL, '김철수');
SELECT emp_seq.CURRVAL FROM DUAL; -- 현재 값 확인
-- MySQL: AUTO_INCREMENT
CREATE TABLE employees (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
INSERT INTO employees (name) VALUES ('김철수');
SELECT LAST_INSERT_ID(); -- 방금 생성된 ID 확인
-- PostgreSQL: SERIAL / IDENTITY
CREATE TABLE employees (
id SERIAL PRIMARY KEY, -- 전통적
name VARCHAR(100) NOT NULL
);
CREATE TABLE employees (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- 표준
name VARCHAR(100) NOT NULL
);시퀀스 (Oracle)
* 독립적 객체로 여러 테이블에서 공유 가능
* CACHE 옵션으로 성능 최적화
* 값에 빈 번호(gap)가 생길 수 있음 (캐시, ROLLBACK)
AUTO_INCREMENT (MySQL)
* 테이블에 종속
* 삭제 후 재사용하지 않음 (1,2,3 중 2 삭제 → 다음은 4)
* 서버 재시작 시 MAX(id)+1로 재계산 (InnoDB)
공통 주의사항
자동 증가 값은 순서를 보장하지만, 연속성은 보장하지 않습니다.
중간에 빈 번호가 생기는 것은 정상이며, 이를 메꾸려 하면 안 됩니다.실습: 쇼핑몰 테이블 생성
-- 회원 테이블
CREATE TABLE users (
user_id NUMBER PRIMARY KEY,
username VARCHAR2(50) NOT NULL UNIQUE,
email VARCHAR2(200) NOT NULL UNIQUE,
password VARCHAR2(200) NOT NULL,
created_at DATE DEFAULT SYSDATE
);
-- 상품 테이블
CREATE TABLE products (
product_id NUMBER PRIMARY KEY,
name VARCHAR2(200) NOT NULL,
price NUMBER(10,2) NOT NULL CHECK (price > 0),
stock NUMBER DEFAULT 0 CHECK (stock >= 0),
category VARCHAR2(50),
description CLOB
);
-- 주문 테이블
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
user_id NUMBER NOT NULL REFERENCES users(user_id),
order_date DATE DEFAULT SYSDATE,
status VARCHAR2(20) DEFAULT 'PENDING'
CHECK (status IN ('PENDING','PAID','SHIPPED','DELIVERED','CANCELLED'))
);
-- 주문 상세 테이블
CREATE TABLE order_items (
item_id NUMBER PRIMARY KEY,
order_id NUMBER NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE,
product_id NUMBER NOT NULL REFERENCES products(product_id),
quantity NUMBER NOT NULL CHECK (quantity > 0),
unit_price NUMBER(10,2) NOT NULL
);스키마 설계 포인트
테이블 설계 시 왜 이런 구조를 선택했는지 이해하는 것이 중요합니다. 각 설계 결정에는 분명한 이유가 있습니다.
1. 주문과 주문 상세를 분리한 이유
하나의 주문에 여러 상품이 포함될 수 있음 (1:N 관계)
정규화: 반복 그룹을 별도 테이블로 분리
2. unit_price를 order_items에 저장하는 이유
상품 가격은 변동될 수 있음
주문 시점의 가격을 보존해야 정확한 매출 계산 가능
3. ON DELETE CASCADE
주문이 삭제되면 주문 상세도 함께 삭제
고아 레코드(orphan record) 방지
4. CHECK 제약
가격 > 0, 재고 >= 0, 수량 > 0
비즈니스 규칙을 DB 수준에서 강제이 스키마는 이후 장에서 계속 실습 예제로 사용됩니다. 실습 시 위 SQL을 직접 실행해보면 DDL의 동작을 체험할 수 있습니다.
핵심 정리
┌────────────────────────────────────────────────────────┐
│ DDL 명령어 │
│ CREATE TABLE: 테이블 생성 │
│ ALTER TABLE: 구조 변경 (컬럼 추가/삭제/수정) │
│ DROP TABLE: 테이블 삭제 │
│ TRUNCATE TABLE: 데이터만 삭제 (ROLLBACK 불가!) │
├────────────────────────────────────────────────────────┤
│ 주요 데이터 타입 │
│ 숫자: NUMBER/INT/BIGINT (정수), DECIMAL (금액) │
│ 문자: CHAR (고정), VARCHAR (가변), TEXT/CLOB (대용량) │
│ 날짜: DATE, TIMESTAMP (밀리초), TIMESTAMPTZ (타임존) │
│ 불리언: BOOLEAN (PG/MySQL), NUMBER(1) (Oracle) │
├────────────────────────────────────────────────────────┤
│ 설계 원칙 │
│ * 적절한 데이터 타입 선택 (공간 절약, 무결성 보장) │
│ * 금액은 반드시 DECIMAL (FLOAT 사용 금지!) │
│ * 일관된 네이밍 컨벤션 사용 │
│ * 제약 조건으로 비즈니스 규칙 강제 │
│ * DDL은 자동 커밋됨 (ROLLBACK 불가!) │
└────────────────────────────────────────────────────────┘다음 절에서는 제약 조건을 더 자세히 다루겠습니다.
테이블 설계는 한번 결정하면 변경 비용이 크므로, 초기 설계 단계에서 충분히 고민하는 것이 중요합니다.