배열과 정규화된 테이블 비교
배열을 선택하는 것이 적절한 경우를 알아봅니다.
배열과 정규화된 테이블 비교은(는) CoddyKit의 무료 SQL Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 SQL Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
여러 값을 저장하는 두 가지 방법
하나의 행에 서로 관련된 여러 값을 저장해야 할 때 PostgreSQL에서는 두 가지 주요 방법을 제공합니다. 같은 행에 배열 열로 저장하거나, 각 값이 자신의 행을 갖는 별도의 하위 테이블을 만드는 방법입니다.
각 방법을 언제 사용할지 이해하는 것은 효율적이고 유지 관리하기 쉬운 데이터베이스를 설계하는 데 중요한 기술입니다.
정규화된 방식
완전히 정규화된 schema에서는 각 데이터가 자신의 행에 저장됩니다. 사용자 한 명이 여러 전화번호를 가질 수 있다면 users를 참조하는 외래 키가 있는 user_phones 테이블을 만듭니다.
이는 전형적인 관계형 모델이며 대부분의 상황에서 기본적으로 선택하는 방식입니다.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE user_phones (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
phone TEXT NOT NULL
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
INSERT INTO user_phones (user_id, phone) VALUES
(1, '+1-555-0101'),
(1, '+1-555-0102'),
(2, '+1-555-0200');배열 방식
PostgreSQL의 TEXT[] 또는 다른 자료형 뒤에 []를 붙인 형태를 사용하면 여러 값을 하나의 열 안에 직접 저장할 수 있습니다. 별도의 테이블이 필요하지 않습니다.
같은 전화번호 데이터를 사용자마다 하나의 간결한 행에 저장할 수 있습니다.
CREATE TABLE users_with_phones (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
phones TEXT[]
);
INSERT INTO users_with_phones (name, phones) VALUES
('Alice', ARRAY['+1-555-0101', '+1-555-0102']),
('Bob', ARRAY['+1-555-0200']);배열 조회는 간단합니다
배열 열 내부를 검색할 때는 ANY 연산자나 @> (포함) 연산자를 사용하면 간단합니다. 간단한 WHERE 절만으로 특정 전화번호를 가진 모든 사용자를 찾을 수 있습니다.
-- Find users who have a specific phone number
SELECT name
FROM users_with_phones
WHERE '+1-555-0101' = ANY(phones);
-- Or using the array-contains operator
SELECT name
FROM users_with_phones
WHERE phones @> ARRAY['+1-555-0101'];배열이 유리한 경우: 간단한 조회
다음과 같은 경우에는 배열이 적합합니다.
- 값 목록을 하나의 단위로 함께 읽는 경우(태그, 라벨, 카테고리)
- 개별 요소를 기준으로 조인할 필요가 전혀 없는 경우
- 목록에 자연스러운 상한이 있고 일부 요소만 수정하는 일이 드문 경우
대표적인 예는 블로그 게시물에 태그를 저장하는 것입니다. 항상 모든 태그를 한 번에 가져오며, 복잡한 조인에서 단일 태그를 기준으로 게시물을 조회하는 일은 드뭅니다.
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[]
);
INSERT INTO posts (title, tags) VALUES
('Intro to SQL', ARRAY['sql', 'beginner', 'database']),
('Advanced Indexes', ARRAY['sql', 'performance', 'indexes']),
('NoSQL Overview', ARRAY['nosql', 'beginner']);
-- Get all posts tagged 'beginner'
SELECT title FROM posts
WHERE 'beginner' = ANY(tags);정규화된 테이블이 유리한 경우: 관계
다음과 같은 경우에는 정규화된 테이블을 선택하는 편이 좋습니다.
- 개별 값에 고유한 속성이 필요한 경우(예: 전화번호에 집 전화 또는 직장 전화라는 유형이 있는 경우)
- 개별 값을 기준으로 조인해야 하는 경우
- 값이 독립적으로 자주 변경되는 경우
- 외래 키를 통한 참조 무결성이 필요한 경우
-- Phone numbers need a 'type' attribute — array can't do this cleanly
CREATE TABLE user_phones (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
phone TEXT NOT NULL,
type TEXT CHECK (type IN ('home', 'work', 'mobile'))
);
INSERT INTO user_phones (user_id, phone, type) VALUES
(1, '+1-555-0101', 'home'),
(1, '+1-555-0102', 'work');인덱싱의 차이
정규화된 테이블에서는 외래 키 열이나 값 열에 표준 B-트리 인덱스를 추가할 수 있습니다. 배열에서는 배열 내부를 빠르게 검색하려면 GIN 인덱스(일반화 역색인)가 필요합니다.
GIN 인덱스는 잘 작동하지만 B-트리 인덱스보다 크기가 크고 업데이트 속도가 느립니다.
-- Index for fast array element lookups
CREATE INDEX idx_posts_tags ON posts USING GIN (tags);
-- Now this query uses the index efficiently
EXPLAIN SELECT title FROM posts
WHERE tags @> ARRAY['sql'];행 전체의 집계: 정규화 방식이 유리합니다
개별 값을 기준으로 개수를 세거나 그룹화하거나 집계해야 할 때는 정규화된 테이블이 훨씬 자연스럽습니다. 배열 내부에서 집계하려면 unnest()를 사용해야 하는데, 이 함수는 먼저 배열을 행으로 펼칩니다. 즉, 질의 시점에 정규화된 구조를 사실상 다시 만드는 셈입니다.
-- Count posts per tag (array approach — needs unnest)
SELECT tag, COUNT(*) AS post_count
FROM posts, unnest(tags) AS tag
GROUP BY tag
ORDER BY post_count DESC;
-- With a normalized post_tags table this would be simpler:
-- SELECT tag, COUNT(*) FROM post_tags GROUP BY tag;배열 요소 수정
배열 안의 단일 요소를 업데이트하거나 삭제하려면 문법이 번거롭습니다. 전체 배열을 바꾸거나 array_remove()를 사용해야 합니다. 정규화된 테이블에서는 특정 행에 대해 간단히 DELETE 또는 UPDATE를 실행하면 됩니다.
-- Remove a single tag from an array column
UPDATE posts
SET tags = array_remove(tags, 'beginner')
WHERE id = 1;
-- Append a new tag
UPDATE posts
SET tags = array_append(tags, 'tutorial')
WHERE id = 1;
SELECT title, tags FROM posts WHERE id = 1;유효한 값 적용
정규화된 테이블에서는 외래 키를 사용해 모든 값이 미리 정해진 집합에서 오도록 강제할 수 있습니다. 배열은 다른 테이블을 참조할 수 없으므로 외래 키를 지원하지 않습니다.
각 요소에 대해 참조 무결성을 보장해야 한다면 하위 테이블만이 유일한 선택입니다.
-- Normalized: only valid category IDs allowed (FK enforced)
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE post_categories (
post_id INT REFERENCES posts(id),
category_id INT REFERENCES categories(id),
PRIMARY KEY (post_id, category_id)
);
-- Array: no constraint possible — any text value is accepted
-- UPDATE posts SET tags = ARRAY['totally_invalid_tag'] WHERE id = 1;실용적인 선택 가이드
데이터가 단순한 평면 목록이고, 항상 하나의 단위로 읽으며, 요소별 추가 속성이 없고, 참조 무결성이 필요하지 않다면 배열을 사용하세요(예: 태그, 라벨, 검색 키워드).
각 요소에 고유한 속성이 있거나, 개별 값을 기준으로 조인 또는 집계를 수행하거나, 외래 키가 필요하거나, 개별 요소를 자주 업데이트하거나 삭제한다면 정규화된 하위 테이블을 사용하세요.
-- Summary example: tags as array (good fit)
SELECT title, tags
FROM posts
WHERE tags @> ARRAY['sql']
ORDER BY title;
-- Unnest when you need row-level processing
SELECT title, unnest(tags) AS tag
FROM posts
ORDER BY title, tag;빠른 확인
정규화된 하위 테이블 대신 PostgreSQL 배열로 데이터를 저장하기에 가장 적합한 상황은 무엇인가요?
레슨 요약
이번 레슨에서는 PostgreSQL에서 배열과 정규화된 테이블을 사용할 때의 주요 장단점을 배웠습니다.
- 배열은 태그처럼 평면적이고 한 단위로 읽는 목록을 간결하고 편리하게 저장할 수 있지만, 외래 키가 없고 요소별 업데이트가 번거로우며 빠른 검색을 위해 GIN 인덱스가 필요합니다.
- 정규화된 테이블은 요소별 속성, 외래 키 무결성, 효율적인 집계, 간단한 행 단위 업데이트를 지원하지만 추가 조인이 필요합니다.
- 올바른 선택은 데이터를 어떻게 저장하는지만이 아니라 어떻게 조회하고, 업데이트하고, 서로 연결하는지에 따라 결정됩니다.
자주 묻는 질문
“배열과 정규화된 테이블 비교” 강의는 무료인가요?
네 — “배열과 정규화된 테이블 비교” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 SQL Academy 강의 전체를 잠금 해제할 수 있습니다. SQL Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“배열과 정규화된 테이블 비교”에서 뭘 배우나요?
배열을 선택하는 것이 적절한 경우를 알아봅니다. 브라우저에서 직접 실행하는 실습 코드로 SQL Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
SQL Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 SQL Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“배열과 정규화된 테이블 비교” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 SQL Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 SQL Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 배열 열 기초
- 배열 내부 검색
- UNNEST와 집계
- 배열과 정규화된 테이블 비교