테이블과 컬럼 이름 짓기
단수냐 복수냐, id 냐 user_id 냐, is_ 냐 has_ 냐 — 지킬 만한 규칙과, 일관되기 때문에만 의미 있는 규칙.
대부분은 일관되기 때문에만 의미가 있다
명명 논쟁의 거의 전부에 방어 가능한 답이 둘씩 있습니다. 테이블 이름의 단수와 복수, 기본키의 id 와 table_id, created_at 과 created_date — 각각 근거가 있고, 어느 쪽도 질의를 느리게 하거나 데이터를 잃지 않습니다.
진짜 시간을 잡아먹는 것은 절반은 이쪽, 절반은 저쪽으로 간 스키마입니다. 질의를 쓰는 사람이 매번 어느 테이블이 어느 규칙인지 찾아봐야 합니다. 그래서 지킬 규칙은 이것입니다 — 하나를 고르고, 적어 두고, 전부를 바꾸는 마이그레이션으로만 바꾼다.
아래는 맞출 기존 스키마가 없을 때 어느 쪽을 고를지입니다. 기존 스키마가 있다면 그것에 맞추세요 — 동의하지 않는 부분이라도요.
테이블
| 규칙 | 예 | 이유 |
|---|---|---|
| 복수 | users, orders, order_items | 테이블은 여러 행을 담고, 한 행이 단수다 |
| snake_case | order_items | Postgres·Oracle 에서 따옴표 없는 식별자는 어차피 소문자로 접힌다 |
| 접두사 없이 | tbl_users 가 아니라 users | 접두사는 맥락이 이미 말한 것을 반복한다 |
| 연결 테이블은 둘을 붙여 | post_tags, listing_amenities | 관계를 대개 타고 가는 순서로 읽힌다 |
| 자라면 이름을 준다 | student_courses 가 아니라 enrollments | 자기 컬럼을 가진 연결 테이블은 개체다 |
복수가 다수 관행이고 대부분의 ORM 이 전제하는 쪽입니다. 단수 쪽의 논거가 더 낫습니다 — 테이블은 집합이고 이름은 그 원소를 가리킨다 — 지만 소수 입장이라 옮겨 가는 비용이 얻는 것보다 큽니다.
키
| 컬럼 | 규칙 | 비고 |
|---|---|---|
| 기본키 | id | 짧고, 자기 테이블 안에서는 모호하지 않다 |
| 외래키 | <단수>_id | user_id, listing_id — 대상 테이블을 이름에 담는다 |
| 같은 테이블로 가는 외래키 둘 | <역할>_id | user_id_1·user_id_2 가 아니라 reviewer_id·reviewee_id |
| 연결 테이블 복합키 | (a_id, b_id) | 쌍이 곧 키. 대리 id 는 더하는 것이 없다 |
| 자연키 | 값 자체 | country_code, currency_code — 값이 안정적이고 의미 있을 때 |
기본키를 id 대신 user_id 로 하자는 논거는 조인이 별칭 없이 읽힌다는 것입니다. 반대 논거는 모든 테이블이 가장 많이 쓰는 컬럼에서 자기 이름을 반복하게 된다는 것입니다. id 가 더 흔하고, 외래키 규칙 — user_id 는 users.id 를 가리킨다 — 이 자명해집니다.
패턴이 있는 컬럼
| 종류 | 규칙 | 예 |
|---|---|---|
| 불리언 | is_ 또는 has_ | is_active, has_paid |
| 사건의 시각 | <동사>_at | created_at, published_at, deleted_at |
| 시각 없는 날짜 | <동사>_date 또는 <명사>_date | due_date, birth_date |
| 개수 | <명사>_count | follower_count, retry_count |
| 금액 | <명사>_amount 또는 <명사>_total | discount_amount, line_total |
| 열거 상태 | status 또는 <명사>_status | status, payment_status |
| 순서 | position 또는 sort_order | 부모 안에서는 position, 전역은 sort_order |
_at 접미사는 제 몫을 합니다. 그 컬럼이 플래그나 기간이 아니라 순간이라는 것을 말해 주는데, 바로 그것이 헷갈리는 지점입니다. deleted_at 은 주석 없이도 소프트 삭제로 읽히고, is_deleted 와 deleted_at 을 같이 두면 한 가지를 두 컬럼이 말하게 됩니다.
피할 단어
- 예약어 — order, user, group, table, key. 따옴표를 붙이면 되지만 영원히 붙이는 것이 세금입니다. orders, users, groups 를 쓰세요.
- 보편적이지 않은 축약 — usr, dt, amt. 아낀 타자는 누군가 처음 잘못 짐작하는 순간 되갚습니다.
- 이름에 든 타입 — name_varchar, is_flag. 타입은 이미 스키마에 있습니다.
- 뜻 없는 접미사 — user_data, order_info. 모든 테이블이 데이터를 담습니다.
- 한 단어를 두 뜻으로 — 같은 스키마에서 status 가 계정 상태이기도 하고 결제 상태이기도 한 경우. 하나는 수식하세요.
사람들이 걸리는 것은 user 입니다. PostgreSQL 에서는 예약어이고 MySQL 에서는 그냥 됩니다. 한쪽에서는 되고 다른 쪽에서는 따옴표가 필요한 스키마는 언젠가 그것을 모르는 사람이 옮기게 됩니다. users 면 이 질문 자체가 없습니다.
물리명과 논리명
물리명은 데이터베이스가 보는 이름 — order_items 입니다. 논리명은 사람이 부르는 이름 — 주문 품목 입니다. 둘 다 두면 SQL 을 쓰지 않는 사람도 도면을 읽을 수 있고, 컬럼 이름을 바꾸지 않고도 용어집을 가질 수 있습니다.
이것이 작동하게 하는 규율은 개념 하나에 용어 하나입니다. 같은 것을 한 곳에서는 '회원', 다른 곳에서는 '사용자' 라고 부르는 도면이라면, 논리명은 용어집이기를 그만두고 두 번째 혼란이 된 것입니다.
자주 묻는 것
- 테이블 이름은 단수인가요 복수인가요?
- 이미 단수인 스키마가 있는 게 아니라면 복수입니다. 다수 관행이고 대부분의 ORM 이 전제합니다. 단수 쪽 논거도 일관되지만 소수 입장이고, 일관성이 그것을 이깁니다.
- 기본키를 id 로 할까요 user_id 로 할까요?
- id 입니다. 가장 많이 읽히는 자리에서 컬럼이 짧게 유지되고, 외래키 규칙 — user_id 는 users.id 를 가리킨다 — 이 자명해집니다. 반대쪽은 조인에서 약간 더 잘 읽히는 대신 모든 곳에서 테이블 이름을 반복합니다.
- 연결 테이블 이름은 어떻게 짓나요?
- 두 테이블을 붙입니다 — post_tags, listing_amenities. 자기 컬럼이 생기면 개체이므로 제 이름을 줄 자격이 있습니다. student_courses 보다 enrollments 입니다.
- 테이블 이름을 user 로 하면 왜 문제인가요?
- PostgreSQL 에서 예약어라 따옴표가 필요하고 MySQL 에서는 그냥 됩니다. 한쪽에서 되고 다른 쪽에서 따옴표가 필요한 스키마는 결국 그 사실을 모르는 사람이 옮기게 됩니다. users 면 비껴갑니다.
- is_deleted 인가요 deleted_at 인가요?
- deleted_at 하나입니다. 여부와 시점에 모두 답하고, 플래그를 더하면 서로 어긋날 수 있는 컬럼이 하나 늘어납니다. deleted_at is null 로 거르세요.