포스트

제약조건에 대해서 (PRIMARY KEY, UNIQUE, NOT NULL, CHECK)

제약조건에 대해서 (PRIMARY KEY, UNIQUE, NOT NULL, CHECK)

검증 로직을 어디에 둘 것인가

신입 때 저는 데이터 검증을 전부 애플리케이션 코드에서 했습니다. “이메일은 중복되면 안 돼”, “나이는 0보다 커야 해” 같은 규칙을 서비스 레이어에서 if문으로 막았죠. 그런데 배치 작업, 다른 관리 도구, 직접 날린 SQL처럼 애플리케이션을 거치지 않는 경로가 생기면 그 검증은 무용지물이 됩니다.

데이터의 무결성은 결국 데이터베이스 자신이 지켜야 가장 확실합니다. PostgreSQL의 제약조건(constraint)이 그 역할을 합니다.

NOT NULL — 값이 반드시 있어야 함

가장 단순한 제약. 해당 컬럼에 NULL을 허용하지 않습니다.

1
2
3
4
CREATE TABLE users (
    id    bigserial PRIMARY KEY,
    email text NOT NULL
);

UNIQUE — 중복 금지

특정 컬럼(또는 컬럼 조합)의 값이 테이블 내에서 유일하도록 보장합니다. UNIQUE 제약을 걸면 PostgreSQL이 내부적으로 유니크 인덱스를 자동으로 만들어줍니다.

1
2
3
4
5
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);

-- 여러 컬럼 조합도 가능 (이 조합이 유일)
ALTER TABLE memberships
    ADD CONSTRAINT uq_user_team UNIQUE (user_id, team_id);

PRIMARY KEY — 행을 대표하는 키

PRIMARY KEY는 사실상 UNIQUE + NOT NULL 의 조합입니다. 테이블당 하나만 둘 수 있고, 각 행을 유일하게 식별합니다.

1
2
3
4
5
6
7
8
9
10
CREATE TABLE orders (
    id      bigserial PRIMARY KEY,            -- 단일 컬럼 PK
    -- ...
);

CREATE TABLE order_items (
    order_id   bigint,
    product_id bigint,
    PRIMARY KEY (order_id, product_id)        -- 복합 PK
);

CHECK — 값의 규칙을 정의

컬럼 값이 특정 조건을 만족해야만 통과시킵니다. 도메인 규칙을 DB에 못 박아 둘 수 있습니다.

1
2
3
4
5
6
7
8
9
CREATE TABLE products (
    id    bigserial PRIMARY KEY,
    price integer NOT NULL CHECK (price >= 0),
    discount_rate numeric CHECK (discount_rate BETWEEN 0 AND 1)
);

-- 테이블 레벨 CHECK: 여러 컬럼 관계도 검증 가능
ALTER TABLE events
    ADD CONSTRAINT chk_event_period CHECK (start_at < end_at);

FOREIGN KEY — 참조 무결성

다른 표를 살짝 곁들이면, FOREIGN KEY는 참조하는 값이 부모 테이블에 실제로 존재하도록 강제합니다.

1
2
3
ALTER TABLE orders
    ADD CONSTRAINT fk_orders_user
    FOREIGN KEY (user_id) REFERENCES users (id);

제약조건에 이름을 붙이자

위 예시들처럼 CONSTRAINT 이름을 직접 지정하는 습관을 들이면 좋습니다. 이름을 안 주면 PostgreSQL이 users_email_key 같은 이름을 자동으로 붙이는데, 나중에 제약을 삭제·변경하거나 에러 로그를 추적할 때 이름이 명확하면 훨씬 편합니다.

1
2
-- 이름이 있으면 관리가 쉽다
ALTER TABLE users DROP CONSTRAINT uq_users_email;

정리

  • 데이터 무결성은 애플리케이션이 아니라 DB가 지키게 하는 게 가장 안전합니다.
  • NOT NULL(필수), UNIQUE(중복 금지), PRIMARY KEY(= UNIQUE + NOT NULL), CHECK(값 규칙), FOREIGN KEY(참조 무결성)를 적절히 조합하세요.
  • 제약조건엔 명시적인 이름을 붙이면 운영이 편해집니다.
  • 다음 글에서는 이 제약조건들을 운영 환경에 적용할 때 자주 마주치는 함정들을 정리하겠습니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.