한양과학기술고등학교

에듀테크 프로젝트 대시보드

JSON / JSONB

jsonb -> ->> @> GIN jsonb_set

반정형 데이터를 컬럼 하나에 담습니다. 저장·조회는 거의 항상 jsonb 를 씁니다.

1. json vs jsonb

jsonjsonb
저장입력 텍스트 그대로파싱된 바이너리
공백·키 순서·중복키보존정규화(중복키는 마지막 값)
연산자 인덱싱불가GIN 가능
쓰기 속도약간 빠름약간 느림 (파싱 비용)

특별한 이유가 없으면 jsonb.

2. 접근 연산자

-- data = {"user": {"name": "김한양"}, "tags": ["a", "b"], "age": 17}
SELECT
  data -> 'user'            AS as_json,    -- {"name": "김한양"}  (jsonb)
  data -> 'user' ->> 'name' AS as_text,    -- 김한양            (text)
  data #>> '{user,name}'    AS by_path,    -- 김한양
  data -> 'tags' ->> 0      AS first_tag,  -- a
  (data ->> 'age')::int     AS age
FROM profiles;

3. 검색과 인덱스

-- 포함(containment) — 가장 자주 쓰는 검색
SELECT * FROM profiles WHERE data @> '{"age": 17}';
SELECT * FROM profiles WHERE data -> 'tags' ? 'a';   -- 키/요소 존재

-- GIN 인덱스
CREATE INDEX idx_profiles_data ON profiles USING gin (data);
-- @> 만 쓸 거면 더 작고 빠른:
CREATE INDEX idx_profiles_data ON profiles USING gin (data jsonb_path_ops);

4. 수정

UPDATE profiles SET data = data || '{"verified": true}'    WHERE id = 1;  -- 병합
UPDATE profiles SET data = jsonb_set(data, '{user,name}', '"이한양"') WHERE id = 1;
UPDATE profiles SET data = data - 'age'                     WHERE id = 1;  -- 키 삭제
UPDATE profiles SET data = data #- '{user,name}'            WHERE id = 1;  -- 경로 삭제
설계 팁
  • 자주 필터·조인·정렬하는 값은 jsonb 안에 두지 말고 정식 컬럼으로 빼세요
  • 생성 열로 승격 가능: age int GENERATED ALWAYS AS ((data->>'age')::int) STORED
"복원을 테스트하기 전까지 그것은 백업이 아니다"