UNION과 집합 결합
열의 모양이 같은 결과 집합을 UNION과 UNION ALL로 세로로 이어, 회원 가입과 게시글 게시라는 두 사건을 하나의 활동 피드로 만듭니다. 분기마다 열의 수와 뜻, 타입을 맞추는 규칙, 행 전체를 비교하는 중복 제거, 결합한 뒤 한 번만 정하는 순서를 다룹니다.
JOIN이 관계가 있는 행의 열을 옆으로 늘린다면, UNION은 이미 완성된 결과 집합을 아래로 이어 행을 늘립니다. 세로로 잇는 것이므로 각 SELECT는 같은 위치에 같은 뜻의 열을 내야 합니다. 열의 수만 맞춘 채 전혀 다른 뜻을 섞으면, 실행은 되어도 받는 쪽이 해석할 수 없는 결과가 됩니다.
회원 게시판의 "최근 활동" 피드에는 회원 가입과 게시글 게시라는 서로 다른 사건이 함께 표시됩니다. 원래의 테이블은 다르지만 event_at, event_type, event_key, summary라는 공통의 결과 형식으로 바꾸면, UNION ALL로 한 목록을 만들 수 있습니다.
두 사건 원본을 같은 네 열의 피드로 투영한다
두 원본의 의미와 타입을 맞춘다. 전체 결합 뒤 순서를 정한다. 각 단계의 값과 전환 이유를 비교합니다.
UNION ALL · SAME FOUR COLUMNS
두 원본을 네 공통 열로 맞춰 1 + 2 = 3행의 피드로
가입 한 건과 게시 두 건을 세로로 합칩니다. 같은 열 위치에 같은 의미·호환 타입을 두고 사건 유형과 원본 키를 함께 보존합니다.
두 입력 화살표는 같은 열 모양으로 만든 사건 행을 결합하는 관계입니다. 가입을 게시글로 변환하는 과정이나 두 원본의 JOIN을 뜻하지 않습니다.
| 공통 열 | 회원 가입 | 게시글 게시 |
|---|---|---|
| event_at · 사건 시각 | created_at | published_at |
| event_type · 사건 유형 | MEMBER_JOINED | POST_PUBLISHED |
| event_key · 원본 키 | id를 CHAR로 변환 | id를 CHAR로 변환 |
| summary · 표시 내용 | name | title |
- event_at · 사건 시각
- 회원 가입: created_at게시글 게시: published_at
- event_type · 사건 유형
- 회원 가입: MEMBER_JOINED게시글 게시: POST_PUBLISHED
- event_key · 원본 키
- 회원 가입: id를 CHAR로 변환게시글 게시: id를 CHAR로 변환
- summary · 표시 내용
- 회원 가입: name게시글 게시: title
두 분기의 키는 모두 CHAR로 변환됩니다. 서로 다른 원본의 같은 숫자 ID는 event_type으로 구분합니다.
결합 뒤 event_at DESC로 정렬한 3행
- 7/13 12:20 · POST_PUBLISHED · 키 2 · EXISTS 복습
- 7/13 09:00 · MEMBER_JOINED · 키 3 · 박준
- 7/12 18:10 · POST_PUBLISHED · 키 1 · JOIN 실습
UNION ALL은 분기의 행을 보존합니다. 최종 순서는 전체 결합 뒤 ORDER BY로 정합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 동일 열 투영부터 최종 정렬까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 관계 설명 | 회원 가입 1행과 게시글 게시 2행은 네 공통 열로 투영한 후 UNION ALL로 3행이 된다. 결과는 결합 뒤에 시각 내림차순과 유형 키로 정렬한다. JOIN처럼 두 원본 행을 대응시켜 열을 붙이지 않는다. |
| 관계 설명 | 가입 원본의 왼쪽 경로는 게시글 원본을 통과하지 않고 UNION ALL로 직접 이어진다. 게시글 원본도 UNION ALL로 이어진다. 두 입력을 합친 3행만 최종 정렬로 향한다. 회원을 게시글로 바꾸는 순서가 아니다. |
| 표의 값 | event_at · 사건 시각. 회원 가입: created_at. 게시글 게시: published_at. event_type · 사건 유형. 회원 가입: MEMBER_JOINED. 게시글 게시: POST_PUBLISHED. event_key · 원본 키. 회원 가입: id를 CHAR로 변환. 게시글 게시: id를 CHAR로 변환. summary · 표시 내용. 회원 가입: name. 게시글 게시: title. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 동일 열 투영 두 원본의 의미와 타입을 맞춘다. |
|
|
| UNION ALL 분기 행을 보존하여 합친다. |
|
|
| 최종 정렬 전체 결합 뒤 순서를 정한다. |
|
|
두 사건 원본을 같은 네 열의 피드로 투영한다
두 원본의 의미와 타입을 맞춘다. 전체 결합 뒤 순서를 정한다. 각 단계의 값과 전환 이유를 비교합니다.
UNION ALL · SAME FOUR COLUMNS
두 원본을 네 공통 열로 맞춰 1 + 2 = 3행의 피드로
가입 한 건과 게시 두 건을 세로로 합칩니다. 같은 열 위치에 같은 의미·호환 타입을 두고 사건 유형과 원본 키를 함께 보존합니다.
두 입력 화살표는 같은 열 모양으로 만든 사건 행을 결합하는 관계입니다. 가입을 게시글로 변환하는 과정이나 두 원본의 JOIN을 뜻하지 않습니다.
| 공통 열 | 회원 가입 | 게시글 게시 |
|---|---|---|
| event_at · 사건 시각 | created_at | published_at |
| event_type · 사건 유형 | MEMBER_JOINED | POST_PUBLISHED |
| event_key · 원본 키 | id를 CHAR로 변환 | id를 CHAR로 변환 |
| summary · 표시 내용 | name | title |
- event_at · 사건 시각
- 회원 가입: created_at게시글 게시: published_at
- event_type · 사건 유형
- 회원 가입: MEMBER_JOINED게시글 게시: POST_PUBLISHED
- event_key · 원본 키
- 회원 가입: id를 CHAR로 변환게시글 게시: id를 CHAR로 변환
- summary · 표시 내용
- 회원 가입: name게시글 게시: title
두 분기의 키는 모두 CHAR로 변환됩니다. 서로 다른 원본의 같은 숫자 ID는 event_type으로 구분합니다.
결합 뒤 event_at DESC로 정렬한 3행
- 7/13 12:20 · POST_PUBLISHED · 키 2 · EXISTS 복습
- 7/13 09:00 · MEMBER_JOINED · 키 3 · 박준
- 7/12 18:10 · POST_PUBLISHED · 키 1 · JOIN 실습
UNION ALL은 분기의 행을 보존합니다. 최종 순서는 전체 결합 뒤 ORDER BY로 정합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 동일 열 투영부터 최종 정렬까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 관계 설명 | 회원 가입 1행과 게시글 게시 2행은 네 공통 열로 투영한 후 UNION ALL로 3행이 된다. 결과는 결합 뒤에 시각 내림차순과 유형 키로 정렬한다. JOIN처럼 두 원본 행을 대응시켜 열을 붙이지 않는다. |
| 관계 설명 | 가입 원본의 왼쪽 경로는 게시글 원본을 통과하지 않고 UNION ALL로 직접 이어진다. 게시글 원본도 UNION ALL로 이어진다. 두 입력을 합친 3행만 최종 정렬로 향한다. 회원을 게시글로 바꾸는 순서가 아니다. |
| 표의 값 | event_at · 사건 시각. 회원 가입: created_at. 게시글 게시: published_at. event_type · 사건 유형. 회원 가입: MEMBER_JOINED. 게시글 게시: POST_PUBLISHED. event_key · 원본 키. 회원 가입: id를 CHAR로 변환. 게시글 게시: id를 CHAR로 변환. summary · 표시 내용. 회원 가입: name. 게시글 게시: title. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 동일 열 투영 두 원본의 의미와 타입을 맞춘다. |
|
|
| UNION ALL 분기 행을 보존하여 합친다. |
|
|
| 최종 정렬 전체 결합 뒤 순서를 정한다. |
|
|
UNION 예제 데이터
피드의 id와 시각이 앞 장의 실행 순서에 따라 달라지지 않도록, 회원 한 명과 게시글 두 개, 태그 세 개를 고정합니다. 회원 가입 한 건과 게시글 게시 두 건을 합치면 피드는 정확히 세 행입니다.
DROP TABLE IF EXISTS ch6_union_post_demo;
DROP TABLE IF EXISTS ch6_union_tag_demo;
DROP TABLE IF EXISTS ch6_union_member_demo;
CREATE TABLE ch6_union_member_demo (
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(80) NOT NULL,
status VARCHAR(16) NOT NULL,
created_at DATETIME(6) NOT NULL
) ENGINE = InnoDB;
CREATE TABLE ch6_union_post_demo (
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
title VARCHAR(80) NOT NULL,
status VARCHAR(16) NOT NULL,
published_at DATETIME(6) NULL
) ENGINE = InnoDB;
CREATE TABLE ch6_union_tag_demo (
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
code VARCHAR(40) NOT NULL UNIQUE,
name VARCHAR(80) NOT NULL,
is_active BOOLEAN NOT NULL
) ENGINE = InnoDB;
INSERT INTO ch6_union_member_demo
(id, email, name, status, created_at)
VALUES
(3, '[email protected]', '박준', 'ACTIVE', '2026-07-13 09:00:00');
INSERT INTO ch6_union_post_demo (id, title, status, published_at)
VALUES
(1, 'JOIN 실습', 'PUBLISHED', '2026-07-12 18:10:00'),
(2, 'EXISTS 복습', 'PUBLISHED', '2026-07-13 12:20:00');
INSERT INTO ch6_union_tag_demo (id, code, name, is_active)
VALUES
(1, 'database', '데이터베이스', TRUE),
(2, 'java', 'Java', TRUE),
(3, 'legacy', '이전 분류', FALSE);열의 구성이 다른 집합
회원에서는 두 열을, 태그에서는 세 열을 골라 UNION해 봅니다. 아래 쿼리의 첫 SELECT는 id, email을, 둘째 SELECT는 id, code, name을 고르므로, 두 분기의 열 수가 서로 다릅니다.
SELECT id, email
FROM ch6_union_member_demo
UNION ALL
SELECT id, code, name
FROM ch6_union_tag_demo;ERROR 1222 (21000): The used SELECT statements have a different number of columns첫 SELECT는 2열, 둘째는 3열이어서 MySQL이 최종 결과의 모양을 정할 수 없습니다. UNION은 이름이 비슷한 열을 알아서 맞추거나 빠진 열을 NULL로 채우지 않으며, 각 분기의 같은 위치가 최종 결과의 한 열이 됩니다. 그래서 열의 수와 호환되는 타입은 쓰는 사람이 고정해야 합니다.
UNION의 호환성은 테이블의 구조가 아니라 SELECT의 결과를 기준으로 판단합니다. 열의 수를 억지로 맞추더라도 첫 위치가 id이고 둘째 위치에 이메일과 이름이 섞인다면, API의 결과 형식이 모호해집니다. 먼저 공통 열의 이름과 타입, 뜻을 정하고 원래의 테이블을 저마다 그 모양으로 바꿉니다.
UNION의 열과 중복의 규칙
모든 분기는 같은 수의 열을 돌려주고, 같은 위치의 타입이 호환되어야 합니다. 최종 결과의 열 이름은 첫 SELECT의 별칭으로 정해지므로, 첫 분기가 공통의 형식을 분명히 보여 주어야 합니다.
UNION은 모든 열이 같은 행을 하나로 줄이고, UNION ALL은 중복을 그대로 둡니다. 두 종류의 사건처럼 분기들이 구별하는 값 때문에 서로 겹칠 수 없다면, UNION ALL이 의도와 비용을 가장 잘 드러냅니다. UNION과 UNION ALL은 표준 SQL입니다.
분기마다 넣은 ORDER BY는 최종 순서를 보장하지 않으므로, 결합한 뒤 마지막의 ORDER BY 하나로 결과의 순서를 정합니다. 분기 안에서 LIMIT으로 후보를 줄이려면 괄호와 분명한 정렬이 필요하고, 그렇게 줄인 결과가 전체의 상위 N개와 같은지 확인해야 합니다. 공통 키의 타입은 MySQL의 암묵적인 변환에 기대지 말고 CAST로 적습니다.
더 깊이: 결과의 타입과 DISTINCT, ALL이 섞일 때의 규칙
결과의 열 이름은 첫 분기에서 가져오지만, 열의 타입과 길이는 모든 분기가 돌려주는 값을 보고 정합니다. MySQL 8.4 매뉴얼의 예에서 SELECT REPEAT('a',1) UNION SELECT REPEAT('b',20)은 둘째 행의 20글자를 자르지 않고 그대로 돌려줍니다. 첫 분기가 짧은 문자열이라고 결과가 그 길이로 잘리지는 않는다는 뜻입니다. 그래도 분기마다 타입이 다르면 결과의 타입을 분기의 값에 맡기게 되므로, 본문처럼 CAST로 공통의 타입을 적어 두는 편이 결과를 예측하기 쉽습니다.
한 쿼리에 UNION과 UNION ALL을 섞으면, 중복을 없애는 DISTINCT 연산이 그 왼쪽에 있는 모든 ALL 연산을 덮어씁니다. 예를 들어 A UNION ALL B UNION C에서는 마지막의 UNION이 A와 B를 합칠 때 남긴 중복까지 없앱니다. 그래서 중복을 그대로 남겨야 하는 분기는 DISTINCT 연산보다 오른쪽에서 UNION ALL로 붙여야 합니다.
분기 안에 넣은 ORDER BY는 LIMIT이 함께 있을 때만 의미가 있습니다. LIMIT 없이 분기 안에 둔 ORDER BY는 어떤 효과도 없으므로 옵티마이저가 아예 없애 버립니다. 분기의 순서를 정해 두었다고 최종 결과가 그 순서로 나오리라 기대할 수 없는 이유가 여기에 있습니다.
공통의 결과 형식
회원 가입과 게시글 게시를 각각 네 열의 사건으로 바꿉니다. 중복을 없애야 한다는 요구가 없으므로 UNION ALL을 씁니다.
SELECT
m.created_at AS event_at,
'MEMBER_JOINED' AS event_type,
CAST(m.id AS CHAR) AS event_key,
m.name AS summary
FROM ch6_union_member_demo AS m
UNION ALL
SELECT
p.published_at AS event_at,
'POST_PUBLISHED' AS event_type,
CAST(p.id AS CHAR) AS event_key,
p.title AS summary
FROM ch6_union_post_demo AS p
WHERE p.status = 'PUBLISHED'
AND p.published_at IS NOT NULL
ORDER BY event_at DESC, event_type, event_key
LIMIT 20;event_at | event_type | event_key | summary
2026-07-13 12:20:00.000000 | POST_PUBLISHED | 2 | EXISTS 복습
2026-07-13 09:00:00.000000 | MEMBER_JOINED | 3 | 박준
2026-07-12 18:10:00.000000 | POST_PUBLISHED | 1 | JOIN 실습두 분기가 같은 사건의 열을 돌려주고, 결합한 결과 전체가 한 번만 정렬되었습니다. 마지막의 ORDER BY와 LIMIT은 결합한 결과 전체에 적용되며, 그 정렬에는 첫 SELECT의 별칭을 씁니다. 같은 숫자의 키라도 event_type과 함께 읽으므로 서로 부딪치지 않습니다.
event_at이 NULL을 허용한다면 정렬에서의 위치를 정하거나 분기에서 빼야 합니다. 피드 한 행의 키는 (event_type, event_key)의 조합이므로, 커서에도 event_key 하나만 넣지 않습니다. 원래 테이블을 더하더라도 공통의 형식을 지키는 한 분기만 늘리면 되지만, 열의 뜻이 분기마다 달라지기 시작하면 별도의 피드 유형이나 이벤트 테이블을 검토합니다.
UNION ALL의 행 수를 확인한다
중복을 없애지 않는 결합은 분기별 행 수의 합이 최종 행 수와 같아야 합니다. 전체에 LIMIT을 적용하기 전의 모습으로 확인합니다.
WITH
member_events AS (
SELECT created_at AS event_at, 'MEMBER_JOINED' AS event_type,
CAST(id AS CHAR) AS event_key
FROM ch6_union_member_demo
),
published_events AS (
SELECT published_at AS event_at, 'POST_PUBLISHED' AS event_type,
CAST(id AS CHAR) AS event_key
FROM ch6_union_post_demo
WHERE status = 'PUBLISHED' AND published_at IS NOT NULL
),
feed AS (
SELECT * FROM member_events
UNION ALL
SELECT * FROM published_events
)
SELECT
(SELECT COUNT(*) FROM member_events) AS member_rows,
(SELECT COUNT(*) FROM published_events) AS published_rows,
(SELECT COUNT(*) FROM feed) AS feed_rows,
(SELECT COUNT(*) FROM feed) =
(SELECT COUNT(*) FROM member_events) +
(SELECT COUNT(*) FROM published_events) AS counts_match;member_rows | published_rows | feed_rows | counts_match
1 | 2 | 3 | 1counts_match가 1이므로 결합에서 사라진 행이 없습니다. UNION으로 바꾸었더니 수가 줄었다면, 고른 열이 모두 같은 사건이 양쪽이나 한 분기 안에 있었다는 뜻이므로, 그 중복이 데이터의 오류인지 의도한 제거인지 판단합니다.
UNION의 중복 제거는 id 한 열만 보는 것이 아니라 최종적으로 고른 열 전체를 비교합니다. 같은 이메일이라도 이름이 다르면 두 행이 남으므로, 특정한 키를 기준으로 하나만 남기려면 UNION 뒤에 윈도 함수로 우선순위를 매기거나 원래 데이터의 정합성을 고쳐야 합니다. UNION은 집합을 합치는 연산이지, 서로 다른 원본에서 최신 행을 골라 주는 도구가 아닙니다.
결합에서 따질 것
현재의 테이블과 과거를 옮겨 둔 테이블을 UNION ALL로 함께 읽는다면, 두 테이블의 기간이 겹치지 않는다는 수명 주기의 규칙이 필요합니다. 겹칠 수 있다면 UNION으로 중복을 무조건 없애기보다, 원본과 버전, 이관의 id로 원인을 추적합니다.
피드의 페이지 처리에서, 분기마다 LIMIT 20을 주는 것과 전체의 상위 20개를 구하는 것은 언제나 같지 않습니다. 분기마다 같은 정렬 기준으로 적어도 N개의 후보를 낸다는 것이 확인될 때만 LIMIT을 분기로 내리고, 실제의 계획을 측정합니다.
정렬 규칙이 다른 분기를 합치면 비교와 정렬에 쓸 규칙을 고르는 단계에서 오류나 암묵적인 변환이 생길 수 있습니다. DECIMAL과 숫자처럼 보이는 문자열, 시각과 날짜 문자열도 공통의 타입을 분명히 적습니다. 분기의 순서를 바꾸면 최종 열의 이름이 첫 SELECT의 별칭을 따라 바뀐다는 점도 기억해 둡니다.
활동 피드는 뒤의 감사 이력과 통계에서 여러 사건의 원본을 합치는 기초가 됩니다. 실제 감사 기록은 원래의 테이블들을 매번 UNION으로 읽는 대신 일관된 이벤트 테이블로 설계할 수 있지만(15장 3편), 이관이나 백필을 하는 동안에는 같은 공통 형식으로 두 결과가 일치하는지 확인합니다. 원본을 구별하는 값과 원본의 키를 남겨 두면 어느 분기가 중복을 만들었는지 추적할 수 있습니다.
| 따져 볼 것 | 질문 |
|---|---|
| 열 | 모든 분기에서 열의 수와 위치, 뜻이 같은가 |
| 타입 | id와 시각, 문자열의 타입을 분명히 맞추었는가 |
| 중복 | 중복의 제거가 업무의 요구인가, UNION ALL이 맞는가 |
| 출처 | 같은 키를 구별할 원본의 판별 값이 있는가 |
| 순서 | 마지막 ORDER BY가 같은 값까지 가른 전체의 순서를 정하는가 |
UNION을 쓰기 전에 최종 결과의 열 형식을 표로 만들고, 각 분기가 그 표의 한 행을 만드는 변환인지 검토합니다. 열의 수를 맞추려고 넣은 NULL 자리에는 이름과 뜻을 적어 둡니다.
정리
- UNION은 같은 열 모양의 결과를 아래로 이어 행의 집합을 만듭니다.
- UNION은 모든 열이 같은 행을 하나로 줄이고, UNION ALL은 원래의 행을 모두 남깁니다.
- 첫 SELECT가 최종 열의 이름을 정하며, 분기마다 같은 위치의 뜻과 타입을 맞춥니다.
- 최종 순서는 결합한 뒤 마지막의
ORDER BY하나로 정합니다.
연습 문제
1. 회원과 태그의 검색 카탈로그
활성 회원과 활성 태그를 하나의 검색 카탈로그로 합쳐 보세요. 결과의 열은 item_type, item_key, label의 세 개이며, 회원은 이메일을, 태그는 코드를 표시 이름으로 씁니다. 중복은 없애지 않고 유형, 표시 이름, 키의 순서로 정렬합니다.
해설 보기
두 원본은 유형으로 구별되므로 UNION ALL을 씁니다. 숫자 id는 공통의 문자열 키로 CAST하고, 첫 SELECT에서 최종 별칭을 고정합니다.
SELECT
'MEMBER' AS item_type,
CAST(id AS CHAR) AS item_key,
email AS label
FROM ch6_union_member_demo
WHERE status = 'ACTIVE'
UNION ALL
SELECT
'TAG' AS item_type,
CAST(id AS CHAR) AS item_key,
code AS label
FROM ch6_union_tag_demo
WHERE is_active = TRUE
ORDER BY item_type, label, item_key;item_type | item_key | label
MEMBER | 3 | [email protected]
TAG | 1 | database
TAG | 2 | java활성 회원 1명과 활성 태그 2개를 더한 3행이 나왔습니다. 비활성 태그 legacy는 분기의 WHERE에서 빠진 것이지, UNION ALL이 없앤 것이 아닙니다.
2. 무엇이 같아야 중복인가
다음 세 쿼리는 각각 몇 행을 돌려주나요? 세 쿼리는 테이블 없이 리터럴로 한 행씩 만들며, 연산자가 UNION인지 UNION ALL인지와 둘째 행의 event_type만 다릅니다.
SELECT 'MEMBER_JOINED' AS event_type, '3' AS event_key
UNION
SELECT 'MEMBER_JOINED', '3';
SELECT 'MEMBER_JOINED' AS event_type, '3' AS event_key
UNION ALL
SELECT 'MEMBER_JOINED', '3';
SELECT 'MEMBER_JOINED' AS event_type, '3' AS event_key
UNION
SELECT 'POST_PUBLISHED', '3';해설 보기
첫 쿼리는 두 행의 모든 열이 같으므로 UNION이 하나로 줄여 1행입니다. 둘째 쿼리는 UNION ALL이므로 같은 행 두 개가 그대로 남아 2행입니다. 셋째 쿼리는 키가 같아도 event_type이 다르므로 UNION도 두 행을 남깁니다. 원본의 id만 같은 것과 최종 행 전체가 같은 것은 다르며, UNION이 보는 것은 뒤의 것입니다.
3. NULL끼리의 중복
SELECT NULL AS x UNION SELECT NULL은 몇 행을 돌려주나요? 3장 4편에서 본 NULL = NULL의 결과와 비교해 설명해 보세요.
해설 보기
한 행을 돌려줍니다. NULL = NULL은 같은지 알 수 없어 NULL이지만, UNION과 DISTINCT가 중복을 판단할 때는 같은 위치의 NULL끼리를 서로 구별되지 않는 값으로 보아 하나로 줄입니다. 그래서 UNION의 중복 비교를 일반적인 =의 알 수 없음 규칙과 같다고 가정하면 안 됩니다. NULL이 들어갈 수 있는 열로 UNION한다면, NULL인 행들이 하나로 합쳐져도 되는지 함께 정합니다.
다음 글 CASE와 VIEW에서는 CASE로 결과에 조건에 따른 뜻을 만들고, 그 조회 규칙을 뷰로 저장하는 방법을 다룹니다.