PostgreSQL 교재 · 12편 / 24편

JSON·jsonb 다루기 — ->·->>·@>

스키마 없는 데이터를 안전하게 — 연산자 5개와 GIN 인덱스.

SQL 기초읽는 시간 7분2026-05-17
jsonb 의 다양한 연산자가 JSON 객체에서 값을 추출하는 도식

API 응답·로그·설정·메타데이터 — "스키마가 자주 바뀌거나 모양이 들쭉날쭉" 한 데이터를 정식 컬럼으로 만들기 부담스러울 때 jsonb 가 답입니다. 12편은 핵심 연산자 5개, jsonb_set, GIN 인덱스, 그리고 "언제 jsonb 가 적절한가" 의 가이드까지.

json vs jsonb — 항상 jsonb

jsonjsonb
저장원본 텍스트 그대로파싱된 바이너리
입력조금 빠름조금 느림 (파싱)
조회·연산느림 (매번 파싱)빠름
키 순서 보존아니오
중복 키유지마지막 것만
인덱싱제한적GIN 강력

거의 항상 jsonb. json 은 "원본을 글자 단위로 보존해야 한다" 같은 특수 사례에만.

테이블 만들고 데이터 넣기

CREATE TABLE products (
  id   BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  meta JSONB NOT NULL DEFAULT '{}'::jsonb
);

INSERT INTO products (name, meta) VALUES
  ('티셔츠', '{"size": "L", "color": "black", "tags": ["sale", "new"]}'),
  ('컵',     '{"size": "M", "color": "white", "tags": ["hot"], "stock": 5}'),
  ('볼펜',   '{"color": "blue"}');

5가지 핵심 연산자

연산자반환예시
->jsonbmeta->'size' → "L"::jsonb
->>textmeta->>'size' → 'L' (따옴표 없음)
#>jsonb (경로)meta#>'{tags,0}' → "sale"::jsonb
#>>text (경로)meta#>>'{tags,0}' → 'sale'
@>booleanmeta @> '{"color":"black"}' → true
-- 색깔만 뽑기
SELECT name, meta->>'color' AS color FROM products;

-- 사이즈 L 만
SELECT name FROM products WHERE meta->>'size' = 'L';

-- 또는 contains 연산자
SELECT name FROM products WHERE meta @> '{"size":"L"}';

-- 태그 첫 번째 항목
SELECT name, meta#>>'{tags,0}' AS first_tag FROM products;

-- 특정 태그 포함 검사 (jsonb 배열)
SELECT name FROM products WHERE meta->'tags' ? 'sale';

-> vs ->> 한 줄 차이. -> 는 jsonb 그대로(따옴표 살아있음), ->> 는 text 로 풀어줌. WHERE 비교는 보통 ->>, 더 깊이 들어가야 하면 ->.

jsonb_set — 부분 수정

-- meta.color 를 'red' 로 바꾸기
UPDATE products
SET    meta = jsonb_set(meta, '{color}', '"red"')
WHERE  id = 1;

-- 깊은 경로
UPDATE products
SET    meta = jsonb_set(meta, '{address,city}', '"서울"', true)
WHERE  id = 1;
-- create_missing=true → 경로 없으면 만듦

-- 키 삭제
UPDATE products SET meta = meta - 'stock' WHERE id = 2;
UPDATE products SET meta = meta #- '{address,city}' WHERE id = 1;

-- 객체 합치기 (||)
UPDATE products SET meta = meta || '{"featured":true}' WHERE id = 1;

GIN 인덱스 — jsonb 검색 가속

-- 전체 jsonb 에 인덱스 (모든 키·값 검색)
CREATE INDEX ix_products_meta ON products USING GIN (meta);

-- @> ? ?| ?& 연산자가 인덱스 사용
SELECT * FROM products WHERE meta @> '{"color":"black"}';   -- 빠름

-- 특정 키만 (path ops 더 작음)
CREATE INDEX ix_products_meta_path ON products USING GIN (meta jsonb_path_ops);

-- 특정 키만 따로 — B-tree 표현식 인덱스
CREATE INDEX ix_products_color ON products ((meta->>'color'));

GIN 인덱스의 비용. 강력하지만 입력 속도가 느려지고, 인덱스 크기도 큽니다. 실제로 검색에 쓰는 jsonb 컬럼에만 두고, 자주 검색하는 단일 키는 표현식 인덱스(B-Tree) 가 더 가벼울 수 있습니다.

jsonb 집계 — 배열로 모으기

-- 카테고리별 jsonb 배열로 합치기
SELECT category_id,
       jsonb_agg(meta) AS all_meta,
       jsonb_object_agg(name, meta) AS by_name
FROM   products
GROUP BY category_id;

-- 배열 풀기 (jsonb_array_elements)
SELECT id, t.tag
FROM   products,
       jsonb_array_elements_text(meta->'tags') AS t(tag);
-- 한 상품의 태그 N 개를 N 행으로 풀어줌

jsonb 의 함정 4가지

1. 자주 검색하는 필드는 정식 컬럼으로. "지난 1년 검색 통계" 같은 핵심 데이터를 jsonb 에 박으면 두고두고 후회합니다. 정규 컬럼이 정렬·인덱스·제약 모두에 유리.

2. 타입 안전성 없음. 어떤 키가 어떤 타입인지 DB 가 모름. 애플리케이션에서 zod 같은 도구로 검증 필요.

3. 부분 업데이트 성능. jsonb_set 은 전체 jsonb 를 다시 씁니다(MVCC 새 버전). 큰 jsonb 의 작은 필드 자주 바꾸면 부하 큼.

4. 인덱스 크기 폭발. 무분별한 GIN 인덱스는 DB 크기를 키웁니다. 정말 검색하는 패턴만 인덱싱.

"이 데이터 jsonb 로 갈까 정식 컬럼으로 갈까" 가이드

특성정식 컬럼jsonb
자주 검색·정렬애매
JOIN/FK 가능
DB 레벨 제약 (NOT NULL/CHECK)제한적
스키마 자주 변경❌ (ALTER)
희소 데이터 (대부분 없음)❌ (NULL 컬럼)
중첩 구조관계 테이블자연스러움

13편 — 뷰·머티리얼라이즈드 뷰

CREATE VIEW vs MATERIALIZED VIEW, REFRESH 와 표현식 인덱스.

📚 PostgreSQL 배우기 교재
이전: 11편 윈도우 함수 · 현재: 12편 (SQL 기초) · 다음 → 13편 뷰 · 진행: 12/24

© 2026 주나이테크(주) @JUNAITECH