워드프레스는 카테고리를 어디에 저장할까?

Overview

안녕하세요. 이번 시간에는 워드프레스가 데이타를 저장하는 방법에 대해서 이야기 해보도록 하겠습니다. 현재 최신 버젼의 워드프레스는 WordPress Version: 6.7.2이고 이하 설명은 해당 버젼을 기준으로 진행됩니다.

게시물이나 페이지를 저장하는 테이블

게시물이나 페이지는 직관적입니다. wp_posts테이블에 저장하고요 테이블 상세는 아래와 같습니다.

mysql> desc wp_posts;
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
Connection id:    30134210
Current database: ???

+-----------------------+---------------------+------+-----+---------------------+----------------+
| Field                 | Type                | Null | Key | Default             | Extra          |
+-----------------------+---------------------+------+-----+---------------------+----------------+
| ID                    | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
| post_author           | bigint(20) unsigned | NO   | MUL | 0                   |                |
| post_date             | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| post_date_gmt         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| post_content          | longtext            | NO   |     | NULL                |                |
| post_title            | text                | NO   |     | NULL                |                |
| post_excerpt          | text                | NO   |     | NULL                |                |
| post_status           | varchar(20)         | NO   |     | publish             |                |
| comment_status        | varchar(20)         | NO   |     | open                |                |
| ping_status           | varchar(20)         | NO   |     | open                |                |
| post_password         | varchar(255)        | NO   |     |                     |                |
| post_name             | varchar(200)        | NO   | MUL |                     |                |
| to_ping               | text                | NO   |     | NULL                |                |
| pinged                | text                | NO   |     | NULL                |                |
| post_modified         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| post_modified_gmt     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| post_content_filtered | longtext            | NO   |     | NULL                |                |
| post_parent           | bigint(20) unsigned | NO   | MUL | 0                   |                |
| guid                  | varchar(255)        | NO   |     |                     |                |
| menu_order            | int(11)             | NO   |     | 0                   |                |
| post_type             | varchar(20)         | NO   | MUL | post                |                |
| post_mime_type        | varchar(100)        | NO   |     |                     |                |
| comment_count         | bigint(20)          | NO   |     | 0                   |                |
+-----------------------+---------------------+------+-----+---------------------+----------------+
23 rows in set (1.94 sec)

주요 필드를 설명하자면 아래와 같습니다:

  • post_type:
    • post: 일반 블로그 글
    • page: 정적 페이지
    • attachment: 이미지, PDF 등 업로드 파일
    • custom_post_type: 커스텀 포스트 타입 (예: product, portfolio)
  • post_status: publish, draft, private, trash 등
  • post_author: 작성자 (user ID)
  • post_parent: 페이지 간 계층 구조에서 부모 ID 지정할 때 사용
  • post_date: 작성일
  • post_content: 본문
  • post_title: 제목

카테고리를 저장하는 방식

wp_terms

우선 게시물을 쓰기 전에 선택하고자 하는 카테고리를 먼저 등록하게 되어 있잖아요? 그 카테고리들을 저장하는 테이블은 바로 wp_terms입니다. 테이블 안에 카테고리 이름(name), 슬러그(slug), 고유 ID(term_id)를 저장하고, 테이블 구조는 아래와 같습니다. 여기서 term_group는 원래 다국어 관련 기능을 위해 설계되었지만 거의 사용되지 않습니다.

mysql> desc wp_terms;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| term_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(200)        | NO   | MUL |         |                |
| slug       | varchar(200)        | NO   | MUL |         |                |
| term_group | bigint(10)          | NO   |     | 0       |                |
+------------+---------------------+------+-----+---------+----------------+

wp_term_taxonomy

사실 위에서 설명한 wp_terms테이블은 카테고리만을 위해 존재하는 것은 아닙니다. 그 안에 태그도 함께 저장할 수 가 있어요. 그렇다면 해당 레코드가 카테고리인지 태그인지는 어떻게 아느냐? 바로 wp_term_taxonomy테이블에서 해당 term이 어떤 분류 유형(taxonomy)인지 정의 (category, post_tag 등)하고 있습니다. 테이블 상세를 보시면 다음과 같습니다.

mysql> desc wp_term_taxonomy;
+------------------+---------------------+------+-----+---------+----------------+
| Field            | Type                | Null | Key | Default | Extra          |
+------------------+---------------------+------+-----+---------+----------------+
| term_taxonomy_id | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| term_id          | bigint(20) unsigned | NO   | MUL | 0       |                |
| taxonomy         | varchar(32)         | NO   | MUL |         |                |
| description      | longtext            | NO   |     | NULL    |                |
| parent           | bigint(20) unsigned | NO   |     | 0       |                |
| count            | bigint(20)          | NO   |     | 0       |                |
+------------------+---------------------+------+-----+---------+----------------+

term_taxonomy_id에 고유ID를 저장하고, term_id에 wp_terms.term_id를 저장합니다. 그리고 taxonomy에 category인지 post_tag인지를 저장하고, description에 설명을 넣을 수 있어요. 그리고 카테고리인 경우 부모카테고리가 있으면 parent필드에 명시합니다. 그리고 마지막으로 count에는 해당 카테고리에 게시글이 몇개인지가 저장됩니다.

wp_term_relationships

이제 어떤 게시글이 어떤 카테고리에 연결이 되는지를 알아야겠죠? 바로 wp_term_relationships테이블에서 어떤 게시물(post) 이 어떤 카테고리(또는 태그 등)에 연결되어 있는지 저장합니다. 테이블의 내용은 엄청 단순합니다.

mysql> desc wp_term_relationships;
+------------------+---------------------+------+-----+---------+-------+
| Field            | Type                | Null | Key | Default | Extra |
+------------------+---------------------+------+-----+---------+-------+
| object_id        | bigint(20) unsigned | NO   | PRI | 0       |       |
| term_taxonomy_id | bigint(20) unsigned | NO   | PRI | 0       |       |
| term_order       | int(11)             | NO   |     | 0       |       |
+------------------+---------------------+------+-----+---------+-------+

Object_id가 바로 post_id가 되고, term_taxonomy_id가 wp_term_taxonomy테이블의 term_taxonomy_id가 됩니다. 여기서 term_order는 하나의 게시물에 여러개의 태그나 카테고리가 걸려있는 경우 보여주는 순서를 정할 수가 있는데 기본적으로는 사용하지 않습니다.

카테고리 + 태그 모두 포함한 SQL 쿼리

위의 관계들을 종합해보면 게시물목록을 불러올 때 사용되는 쿼리는 다음과 같습니다.

SELECT 
    p.ID AS post_id,
    p.post_title,
    tt.taxonomy,
    t.name AS term_name,
    t.slug AS term_slug
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 p.post_status = 'publish'
  AND p.post_type = 'post'
  AND tt.taxonomy IN ('category', 'post_tag')
ORDER BY p.ID, tt.taxonomy;

그리고 쿼리결과는 아래와 같습니다.

+---------+---------------------+----------+-----------+------------+
| post_id | post_title          | taxonomy | term_name | term_slug  |
+---------+---------------------+----------+-----------+------------+
|       1 | Hello world!        | category | 테크       | tech       |
|       6 | 테스트 포스트           | category | 부동산     | realestate |
|       8 | 뉴스 테스트            | category | 주식       | stocks     |
+---------+---------------------+----------+-----------+------------+

워드프레스의 테이블 형태를 이해하는데 도움이 되셨기를 바랍니다. 그럼 다음시간에 뵐게요!