* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
(워드프레스 해부 ⑫) 분류
카테고리와 태그. 겉보기엔 단순한데, DB에선 세 개(엄밀히는 meta까지 네 개) 테이블에 나뉘어 저장된다. 처음 보면 왜 이렇게 복잡하게 쪼갰나 싶은데, 이유를 알면 워드프레스 설계의 깔끔함이 보인다.
wp_terms— 이름과 슬러그. 순수하게 “뉴스”라는 단어 자체. (term_id, name, slug)wp_term_taxonomy— 그 단어가 무엇인지. 카테고리인지 태그인지(taxonomy), 부모는 누구인지(parent), 글이 몇 개 달렸는지(count).wp_term_relationships— 어떤 글에 그 텀이 붙었는지. 글 ID ↔ term_taxonomy_id 연결.
왜 쪼갰냐면, “뉴스”라는 같은 단어를 카테고리로도 태그로도 쓸 수 있어야 하기 때문이다. wp_terms엔 “뉴스”가 한 번만 있고, wp_term_taxonomy가 그걸 “카테고리 뉴스”와 “태그 뉴스”로 각각 의미 부여한다. 그리고 wp_term_relationships가 실제 글들과 연결한다. 단어 / 의미 / 연결을 분리한, 잘 정규화된 설계다.
직접 해보기
# 카테고리 하나를 세 테이블에 걸쳐 추적
$ wp db query "
SELECT t.term_id, t.name, tt.taxonomy, tt.count
FROM wp_terms t
JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
WHERE tt.taxonomy='category'"
# 특정 카테고리에 속한 글들 (relationships 로 연결)
$ wp db query "
SELECT p.post_title FROM wp_posts p
JOIN wp_term_relationships tr ON p.ID = tr.object_id
JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tt.taxonomy='category' AND tt.term_id = 5"
✅ 확인 — 카테고리 하나를 골라
wp_terms(이름) →wp_term_taxonomy(카테고리 여부·글 수) →wp_term_relationships(연결된 글) 순으로 따라가 보자. 세 테이블이term_id와term_taxonomy_id로 이어지는 게 보이면, 이 정규화 구조를 이해한 거다.
카테고리 vs 태그 — DB에선 taxonomy 값만 다르다
놀랍게도 둘은 같은 테이블에 산다. 차이는 wp_term_taxonomy의 taxonomy 칼럼값 — 'category'냐 'post_tag'냐 — 뿐이다. 그리고 카테고리는 parent(계층)를 쓰고 태그는 안 쓴다. register_taxonomy로 만드는 커스텀 택소노미도 이 자리에 새 taxonomy 이름을 넣는 것일 뿐이다.
| 카테고리 | 태그 | |
|---|---|---|
| taxonomy 값 | category | post_tag |
| 계층(parent) | 있음 | 없음 |
| 성격 | 구조적 분류 | 자유 키워드 |
term 하나가 붙는 과정
글에 ‘뉴스’ 카테고리를 붙이면 세 테이블이 함께 움직인다. terms에 ‘뉴스’가 있고(없으면 생성), term_taxonomy에 그게 category라는 행이 있으며, term_relationships에 이 글 ID와 그 term_taxonomy_id가 연결된다. 특정 카테고리의 글을 뽑는 쿼리를 보면 이 조인이 한눈에 들어온다.
SELECT p.post_title
FROM wp_posts p
JOIN wp_term_relationships tr ON p.ID = tr.object_id
JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
JOIN wp_terms t ON tt.term_id = t.term_id
WHERE tt.taxonomy = 'category' AND t.slug = 'news';
실물 행으로 — 이 블로그의 분류 테이블
이 블로그 DB에서 실제 행을 몇 개 꺼내보면 구조가 손에 잡힌다.
SELECT t.term_id, t.name, t.slug, tt.taxonomy, tt.count
FROM wp_terms t JOIN wp_term_taxonomy tt USING(term_id) LIMIT 3;
-- term_id name slug taxonomy count
-- 15 AI ai category 19
-- 98 자바 java category 24
-- 174 에이전트 agent category 0 ← ?
마지막 행이 재밌다. ‘에이전트’ 카테고리엔 글 12편이 걸려 있는데 count가 0이다 — 전부 예약(future) 상태라서다. count는 ‘연결된 행 수’가 아니라 공개된 글 수를 세는 캐시 값이고, 예약 글이 발행되는 순간 워드프레스가 이 숫자를 갱신한다. 이 캐시 성격 때문에 아래의 ‘카운트 어긋남’ 문제가 생기는 것이기도 하다.
커스텀 택소노미도 같은 자리에 산다
‘난이도’, ‘사용 기술’ 같은 나만의 분류를 만들어도 새 테이블이 생기지 않는다 — taxonomy 칼럼에 새 이름이 들어갈 뿐이다.
register_taxonomy( 'difficulty', 'post', array(
'label' => '난이도',
'hierarchical' => true, // true면 카테고리형(parent 사용), false면 태그형
'show_in_rest' => true, // 블록 에디터에 노출
) );
// 글에 붙이기 — 세 테이블 관계는 API가 알아서 맞춘다
wp_set_object_terms( $post_id, array( '초급' ), 'difficulty' );
hierarchical 하나로 카테고리형/태그형이 갈리는 것도, 위에서 본 ‘카테고리와 태그는 같은 구조’라는 사실의 자연스러운 귀결이다.
고아 텀·카운트 어긋남
실무 함정 하나. 글을 직접 지우거나 마이그레이션하면 count가 실제와 어긋날 수 있다. 이럴 땐 재계산한다. 그리고 테이블을 직접 건드리지 말고 get_terms·wp_set_object_terms 같은 API를 쓰면, 이 세 테이블의 관계를 코드가 알아서 맞춰준다.
wp term recount category post_tag # WP-CLI로 카운트 재계산
이걸로 데이터베이스 모듈 끝. 워드프레스가 콘텐츠·설정·사용자·분류를 어디에 어떻게 담는지 지도가 완성됐다. 다음 모듈부터는 이 데이터를 움직이는 엔진 — 훅, WP_Query, 템플릿 —을 뜯는다. 진짜 워드프레스가 “동작”하는 방식이다.
자주 묻는 질문 (FAQ)
카테고리·태그는 어느 테이블에 저장되나요?
wp_terms·wp_term_taxonomy·wp_term_relationships 세 테이블에 걸쳐 저장됩니다. 용어·분류 체계·글과의 연결이 분리돼 있습니다.
카테고리와 태그는 DB에서 구별되나요?
같은 구조를 쓰되 term_taxonomy의 taxonomy 값(category/post_tag)으로 구분됩니다.

답글 남기기