백엔드 데이터 인프라 9편 — CREATE TABLE 첫 테이블 만들기

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 9편. CREATE TABLE로 PostgreSQL의 첫 테이블을 만드는 표준 패턴과 데이터 타입·제약·기본 키 설계 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 9편 — CREATE TABLE 첫 테이블 만들기

이 글은 백엔드 데이터 인프라 시리즈 70편 중 9편이에요. 8편 SQL 기초 까지 "동사 5개" 를 다뤘으니, 이번 9편은 그 동사들이 작동할 그릇 — 테이블 을 직접 만들어요.

CREATE TABLE — DDL의 핵심

8편 의 5대 동사가 DML (데이터 조작) 이라면, CREATE TABLEDDL (Data Definition Language — 데이터 구조 정의). "이 시스템에 어떤 데이터가 어떤 형태로 박힐 것인가" 의 첫 선언.

기본 형태:

CREATE TABLE 테이블이름 (
    컬럼1 타입 제약,
    컬럼2 타입 제약,
    ...
);

가장 단순한 예

CREATE TABLE users (
    id      BIGSERIAL PRIMARY KEY,
    name    TEXT NOT NULL,
    email   TEXT UNIQUE,
    created TIMESTAMPTZ DEFAULT NOW()
);

이 한 문장이 표현하는 것: - 테이블 이름 = users - 컬럼 4개 — id·name·email·created - 각 컬럼의 타입·제약 명시

PG 자주 쓰는 타입

32편 데이터 타입 에서 깊이 다루지만 — 입문 단계에서 자주 쓰는 6가지.

타입 의미
BIGSERIAL 자동 증가 정수 (64bit) id
INTEGER / INT 32bit 정수 카운트·금액
BIGINT 64bit 정수 외래 키 (BIGSERIAL 가리킬 때)
TEXT 가변 길이 문자열 (사실상 무제한) 이름·이메일·내용
VARCHAR(N) 길이 제한 문자열 사용 안 권장 — TEXT 우세
BOOLEAN true/false 활성 여부
TIMESTAMPTZ 시간대 포함 시각 created·updated
DATE 날짜만 생일·시작일
NUMERIC(p,s) 정확한 소수 금액
JSONB 인덱스 가능 JSON 동적 메타데이터

TEXT vs VARCHAR — 한국에서 자주 헷갈리는

MySQL 출신자가 VARCHAR(255) 자주 박는데 — PG는 TEXT 표준. 길이 제한은 CHECK 제약으로 별도 박는 게 PG 스타일.

CREATE TABLE posts (
    id    BIGSERIAL PRIMARY KEY,
    title TEXT NOT NULL CHECK (char_length(title) <= 200)
);

PG 내부적으로는 TEXT·VARCHAR·CHAR가 거의 같은 성능. 그래서 TEXT가 가장 단순.

제약 (Constraint) — 데이터 무결성

NOT NULL

name TEXT NOT NULL

값이 반드시 박혀야. NULL 박으면 거부.

UNIQUE

email TEXT UNIQUE

다른 행과 같은 값 X. 자동으로 인덱스 생성.

PRIMARY KEY

id BIGSERIAL PRIMARY KEY

= NOT NULL + UNIQUE + 자동 인덱스 + "이 컬럼이 행 식별자" 명시. 테이블당 1개.

DEFAULT

created TIMESTAMPTZ DEFAULT NOW()
status TEXT DEFAULT 'PENDING'

INSERT 시 값 누락이면 기본값 박힘.

CHECK

amount INTEGER CHECK (amount >= 0)
age    INTEGER CHECK (age BETWEEN 0 AND 150)

조건 위반하는 값 거부.

FOREIGN KEY

user_id BIGINT NOT NULL REFERENCES users(id)

다른 테이블 기본 키 참조. 7편 관계형 모델 의 참조 무결성 보장.

다중 컬럼 제약

CREATE TABLE user_tags (
    user_id BIGINT,
    tag_id  BIGINT,
    PRIMARY KEY (user_id, tag_id),                       -- 복합 키
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (tag_id) REFERENCES tags(id)
);

여러 컬럼 합쳐서 키·외래 키 형성.

실전 예 — orders 테이블

CREATE TABLE orders (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT NOT NULL REFERENCES users(id),
    amount      INTEGER NOT NULL CHECK (amount >= 0),
    status      TEXT NOT NULL DEFAULT 'PENDING'
                    CHECK (status IN ('PENDING', 'PAID', 'CANCELED', 'SHIPPED', 'DELIVERED')),
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

이 한 테이블 안에 — 기본 키·외래 키·NOT NULL·CHECK·DEFAULT 가 다 사용됨. 한국 회사 백엔드의 표준 패턴.

IF NOT EXISTS — 멱등성

CREATE TABLE IF NOT EXISTS users (
    id   BIGSERIAL PRIMARY KEY,
    ...
);

같은 이름 테이블 이미 있으면 "무시". CI·마이그레이션 스크립트에 표준.

테이블 수정 — ALTER TABLE

컬럼 추가

ALTER TABLE users ADD COLUMN nickname TEXT;

-- 기본값과 함께
ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'ACTIVE';

컬럼 수정

-- 타입 변경
ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(100);

-- NOT NULL 추가·제거
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;

-- 기본값 변경
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'PENDING';

컬럼 삭제

ALTER TABLE users DROP COLUMN nickname;

컬럼 이름 변경

ALTER TABLE users RENAME COLUMN created TO created_at;

제약 추가·삭제

ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);
ALTER TABLE users DROP CONSTRAINT users_email_unique;

운영 환경 ALTER = "테이블 락 위험" 으로 신중. 큰 테이블은 트랜잭션·다운타임 고려.

DROP TABLE

DROP TABLE users;

-- 안전
DROP TABLE IF EXISTS users;

-- 외래 키로 참조하는 테이블도 함께
DROP TABLE users CASCADE;

CASCADE = 의존하는 객체 함께 삭제. 신중.

테이블 구조 확인 — psql 메타

\dt              -- 테이블 목록
\d users         -- users 구조
\d+ users        -- 더 자세히 (통계·인덱스 포함)

\d users 출력:

                                Table "public.users"
 Column |           Type           | Collation | Nullable |        Default
--------+--------------------------+-----------+----------+-------------------------
 id     | bigint                   |           | not null | nextval('users_id_seq')
 name   | text                     |           | not null |
 email  | text                     |           |          |
 created| timestamp with time zone |           |          | now()
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_key" UNIQUE CONSTRAINT, btree (email)

CREATE TABLE 시 자동 생성된 시퀀스·인덱스·제약이 한눈에.

Spring JPA 매핑

자바 백엔드 입문 45편 @Entity 의 자바 코드가 — 이 CREATE TABLE을 어떻게 생성하나.

@Entity
@Table(name = "users")
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(unique = true)
    private String email;

    @CreationTimestamp
    private LocalDateTime created;
}

→ Hibernate가 자동 생성하는 SQL (ddl-auto: update 모드):

CREATE TABLE users (
    id     BIGSERIAL PRIMARY KEY,
    name   VARCHAR(255) NOT NULL,
    email  VARCHAR(255) UNIQUE,
    created TIMESTAMP
);

@EntityCREATE TABLE 1:1 매핑이 명확해야 — 운영 단계에서 "DB랑 코드 어긋남" 잡을 수 있어요.

함정 5가지

(1) VARCHAR(255) 습관

MySQL 출신자의 습관. PG는 TEXT 우세 — 길이 제한은 CHECK로.

(2) id 컬럼 누락

기본 키 없는 테이블 = "임시 데이터 또는 안티패턴". 거의 모든 테이블에 id 박기.

(3) created_at·updated_at 누락

운영 디버깅 필수 컬럼. 모든 테이블에 박는 게 표준.

(4) DEFAULT NOW() vs @CreationTimestamp

DB 레벨 DEFAULT가 안전 — 앱이 박지 않아도 자동. 단 — JPA가 INSERT 시 NULL 박지 않도록 @PrePersist 또는 Hibernate 어노테이션 매핑.

(5) ALTER TABLE 락

큰 테이블에 ALTER COLUMN ... TYPE 등 박으면 — 테이블 전체 락 (수십 분~ 시간). 운영 환경은 온라인 DDL (pg_repack·Sleek·Liquibase 등) 또는 점진적 마이그레이션.

🎯 거의 모든 테이블 표준 컬럼

id BIGSERIAL PRIMARY KEY + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(). 이 세 컬럼이 운영 디버깅 + 감사 추적의 기본.

한 줄 정리 — CREATE TABLE = DDL 핵심. BIGSERIAL·TEXT·TIMESTAMPTZ + NOT NULL·UNIQUE·PRIMARY KEY·DEFAULT·CHECK·REFERENCES 가 표준 제약 6개. id·created_at·updated_at 3 컬럼이 거의 모든 테이블 표준. ALTER는 운영에서 락 주의.

시험 직전 한 번 더 — CREATE TABLE 입문자가 매번 헷갈리는 것

  • CREATE TABLE name (col type constraint, ...)
  • BIGSERIAL = 자동 증가 64bit 정수 (기본 키 표준)
  • TEXT = PG 문자열 표준 (VARCHAR 거의 안 씀)
  • TIMESTAMPTZ = 시간대 포함 시각
  • NUMERIC(p,s) = 정확한 소수 (금액)
  • 제약 6개 = NOT NULL·UNIQUE·PRIMARY KEY·DEFAULT·CHECK·REFERENCES
  • PRIMARY KEY = NOT NULL + UNIQUE + 자동 인덱스
  • UNIQUE = 자동 인덱스
  • 외래 키 = REFERENCES users(id)
  • 다중 컬럼 키 = PRIMARY KEY (a, b)
  • DEFAULT NOW() = INSERT 시 자동
  • IF NOT EXISTS = 멱등성
  • ALTER ADD COLUMN·ALTER COLUMN·DROP COLUMN·RENAME
  • DROP TABLE [IF EXISTS] [CASCADE]
  • 표준 컬럼 3개 = id · created_at · updated_at
  • \dt 테이블 목록, \d table 구조
  • @Entity ↔ CREATE TABLE 1:1 매핑
  • VARCHAR(255) = MySQL 습관, PG는 TEXT
  • 운영 ALTER = 테이블 락 주의
  • 큰 테이블 = 온라인 DDL 도구 (pg_repack 등)
  • 시퀀스 자동 생성 = users_id_seq
  • CASCADE = 의존 객체 함께 (위험)
  • CHECK 조건 = 데이터 무결성 강화
  • 한국 회사 표준 = TEXT + CHECK 조합

시리즈 다른 편

시리즈 다음 글

다음 글(10편)에서는 데이터 입력 — INSERT 표준 패턴 — 단건·여러건·SELECT INSERT·UPSERT.

공식 문서: PostgreSQL 18 — Tutorial: Creating a New Table에서 더 자세한 사양을 확인할 수 있어요.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!