데이터베이스·스키마·테이블 만들기
DB → 스키마 → 테이블 순서로 만들고, 명명 규칙·DROP 의 주의·권한 한 줄까지.
PostgreSQL 안에서 데이터는 계층 구조로 정리됩니다. 가장 바깥에 데이터베이스(database), 그 안에 스키마(schema), 그 안에 테이블(table)·뷰·함수가 있습니다. 3편에서는 이 세 계층을 직접 만들고, 적절한 이름을 짓고, 안전하게 지우는 법까지 정리합니다.
계층 구조 — DB ⊃ 스키마 ⊃ 테이블
계층 한 줄. 클러스터(서버 한 대) → 데이터베이스 N 개 → 스키마 N 개(기본 public) → 테이블·뷰·함수 N 개.
실무에서는 서비스 하나 = DB 하나가 가장 흔합니다. 한 DB 안의 모듈(인증·결제·로그 등) 을 스키마로 나누는 경우도 많습니다.
CREATE DATABASE
postgres=# CREATE DATABASE myshop
OWNER junai_app
ENCODING 'UTF8'
LC_COLLATE 'ko_KR.UTF-8'
LC_CTYPE 'ko_KR.UTF-8'
TEMPLATE template0;
CREATE DATABASE
-- 접속
postgres=# \c myshop
You are now connected to database "myshop"
myshop=#
한국어 정렬·대소문자 처리에 LC_COLLATE/LC_CTYPE 가 영향을 줍니다. 한국어 환경이라면 ko_KR.UTF-8 을 권장. TEMPLATE template0 은 "빈 베이스에서 시작" 이라는 뜻으로, locale 충돌을 피하는 안전한 기본값입니다.
CREATE SCHEMA — 모듈 분리
myshop=# CREATE SCHEMA shop; -- 상품·주문 모듈
myshop=# CREATE SCHEMA auth; -- 회원·세션 모듈
myshop=# CREATE SCHEMA log; -- 로그 보관
-- 스키마 안에 테이블
myshop=# CREATE TABLE shop.products (...);
myshop=# CREATE TABLE auth.users (...);
-- 매번 shop. 을 안 적으려면 search_path 설정
myshop=# SET search_path TO shop, public;
myshop=# CREATE TABLE orders (...); -- shop.orders 가 됨
스키마는 "네임스페이스" 라고 생각하면 됩니다. shop.users 와 auth.users 가 같은 DB 안에서 충돌 없이 공존할 수 있게요. 작은 서비스는 public 하나만 써도 충분합니다.
CREATE TABLE — 첫 테이블
CREATE TABLE shop.products (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
price NUMERIC(10,2) NOT NULL CHECK (price >= 0),
stock INTEGER NOT NULL DEFAULT 0,
attributes JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ
);
-- 빠른 조회용 인덱스
CREATE INDEX ix_products_created ON shop.products (created_at DESC);
CREATE INDEX ix_products_attrs ON shop.products USING GIN (attributes);
이 한 정의 안에 4편(자료형)·5편(CRUD)·16편(인덱스) 의 미리보기가 다 들어 있습니다.
BIGSERIAL— 자동 증가 정수 PK (1, 2, 3...)NOT NULL— 비어 있을 수 없음UNIQUE— 같은 값 중복 금지CHECK (price >= 0)— 값 제약DEFAULT— 입력 안 하면 기본값JSONB— 구조 자유 데이터(12편 에서 자세히)TIMESTAMPTZ— 시간대 인식 시각
명명 규칙 — 미래의 나를 위해
이름은 한 번 정하면 바꾸기 어렵습니다. 처음부터 일관되게.
| 대상 | 관례 | 예시 |
|---|---|---|
| 테이블 | snake_case · 복수형 | users, order_items |
| 컬럼 | snake_case · 짧고 명확 | created_at, total_price |
| PK | id 또는 {table}_id | id, user_id |
| FK | {참조테이블}_id | products.user_id → users.id |
| 인덱스 | ix_{table}_{cols} | ix_products_created |
| 유니크 | uq_{table}_{cols} | uq_users_email |
| 제약 | ck_{table}_{rule} | ck_products_price_pos |
예약어 충돌 주의. user, order, group, type 등은 SQL 예약어입니다. 컬럼/테이블 이름으로 쓰면 매번 큰따옴표로 감싸야 해서 짜증납니다. users(복수), account, group_name 처럼 살짝 다르게 짓는 게 정신 건강에 좋습니다.
DROP — 안전하게 지우기
-- 테이블
DROP TABLE shop.products; -- 없으면 에러
DROP TABLE IF EXISTS shop.products; -- 없어도 통과 (안전)
-- 참조하는 외래키도 같이
DROP TABLE shop.products CASCADE; -- ⚠️ 다른 곳도 같이 날아감
-- 스키마 통째
DROP SCHEMA shop CASCADE; -- ⚠️ 안의 모든 테이블 삭제
-- 데이터베이스 (현재 접속 중인 DB 는 못 지움)
\c postgres
DROP DATABASE myshop;
운영 DB 에서 절대 금지. DROP ... CASCADE 와 DROP DATABASE 는 되돌릴 수 없습니다. 운영 환경에서는 먼저 백업(20편), 트랜잭션 안에서 시험(14편), 권한 분리(따로 deploy 계정) 가 필수. 학습용 docker 컨테이너에서는 자유롭게 실험해도 OK — 컨테이너 지우면 끝.
4편 — 데이터 타입 7묶음
int·numeric·text·varchar·boolean·uuid·jsonb·timestamptz·array — 어떤 데이터에 무엇을 골라야 후회가 없는가.
이전: 2편 psql · 현재: 3편 (입문) · 다음 → 4편 자료형 · 진행: 3/24