명령어/DB

[PostgreSQL] jsonb_set — 문서 전체 덮어쓰기 없이 JSONB 한 필드만 갱신한다

jykim23 2026. 7. 22. 23:13
반응형

설치·접속: PostgreSQL 설치와 접속

부제: 사용자 설정 JSONB에서 notifications.email 플래그 하나만 토글하고 싶은데, 문서 전체를 앱에서 읽고 다시 쓰는(read-modify-write) 방식은 그사이 다른 갱신을 덮어쓸 위험이 있을 때

jsonb_set 은 경로를 지정해 문서의 그 부분만 갱신한다. 앱이 JSONB를 통째로 읽어 수정하고 다시 UPDATE하는 대신, UPDATE ... SET prefs = jsonb_set(prefs, '{경로}', 새값) 한 문장으로 끝낸다. 나머지 필드는 DB 안에서 그대로 보존되므로 동시 갱신이 서로를 덮어쓸 창이 좁아진다.

CREATE TABLE settings (user_id int primary key, prefs jsonb);
INSERT INTO settings VALUES
 (1, '{"theme":"dark","notifications":{"email":true,"push":false},"tags":["a","b"]}');

-- notifications.email 하나만 false로. 나머지는 건드리지 않는다
UPDATE settings
SET prefs = jsonb_set(prefs, '{notifications,email}', 'false', false)
WHERE user_id = 1
RETURNING prefs->'notifications' AS notifications;
          notifications
---------------------------------
 {"push": false, "email": false}
(1 row)

경로 '{notifications,email}' 은 중첩 키를 배열로 지정한 것이고, 세 번째 인자 'false' 는 새 JSON 값, 마지막 false 는 "경로가 없으면 만들지 마라"(create_missing 끄기)다. email 만 true→false로 바뀌고 push·theme·tags 는 손대지 않았다. 앱이 문서를 읽어와 수정 후 통째로 쓰는 경로를 없앴으니, 두 요청이 겹쳐도 서로의 다른 필드 변경을 날리지 않는다. 함정은 create_missing을 켠(기본 true) 채 오타 난 경로를 넣으면 엉뚱한 새 키가 생긴다는 것이라, 기존 필드만 갱신할 땐 마지막 인자를 false 로 두는 게 안전하다.

이렇게도 쓴다

현재 불리언 값을 읽어 반대로 뒤집는 동적 토글. 앱이 현재 상태를 몰라도 된다.

UPDATE settings
SET prefs = jsonb_set(prefs, '{notifications,email}',
                      to_jsonb(NOT (prefs#>'{notifications,email}')::boolean))
WHERE user_id = 1
RETURNING prefs#>'{notifications,email}' AS email_flag;   -- true

 

jsonb_insert로 배열의 특정 위치에 새 요소를 끼운다. after=true면 지정 인덱스 뒤에.

SELECT jsonb_insert('{"tags":["a","b"]}', '{tags,1}', '"NEW"', true);
-- {"tags": ["a", "b", "NEW"]}

 

여러 필드를 한 UPDATE에서 중첩해 갱신한다. jsonb_set을 감싼다.

UPDATE settings
SET prefs = jsonb_set(jsonb_set(prefs,'{theme}','"light"'), '{notifications,push}','true')
WHERE user_id = 1;

 

|| 연산자로 최상위 키를 병합·추가한다. 얕은 병합이라 중첩 객체는 통째 교체됨에 주의.

UPDATE settings SET prefs = prefs || '{"lang":"ko"}' WHERE user_id = 1;

 

  • 연산자로 키나 배열 요소를 제거한다. 경로 삭제는 #- 사용.
    UPDATE settings SET prefs = prefs #- '{tags,0}' WHERE user_id = 1;  -- tags 첫 요소 제거

 

jsonb_set_lax로 새 값이 NULL일 때 정책(use_json_null/delete_key/raise_exception 등)을 고른다.

SELECT jsonb_set_lax('{"a":1}', '{a}', NULL, true, 'delete_key');  -- {}
반응형