Postgres 9.4에서 JSONB 유형의 열에 대해 업데이트 작업을 수행하는 방법


132

Postgres 9.4 데이터 유형 JSONB에 대한 문서를 살펴보면 JSONB 열에 대한 업데이트 방법을 즉시 알 수 없습니다.

JSONB 유형 및 기능에 대한 문서 :

http://www.postgresql.org/docs/9.4/static/functions-json.html http://www.postgresql.org/docs/9.4/static/datatype-json.html

예를 들어 다음과 같은 기본 테이블 구조가 있습니다.

CREATE TABLE test(id serial, data jsonb);

다음과 같이 삽입이 쉽습니다.

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

이제 '데이터'열을 어떻게 업데이트합니까? 잘못된 구문입니다.

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

내가 놓친 것이 분명한 곳에 문서화되어 있습니까? 감사.

답변:


32

관계형 데이터베이스 내에서 조작하려는 구조화 된 일반 데이터에 JSON 문서를 사용하지 않는 것이 이상적입니다. 용도 정규화 관계형 디자인을 대신.

JSON은 주로 RDBMS 내부에서 조작 할 필요가없는 전체 문서를 저장하기위한 것입니다. 관련 :

Postgres에서 행을 업데이트하면 항상 전체 행 의 새 버전이 작성 됩니다. 이것이 Postgres의 MVCC 모델 의 기본 원칙입니다 . 성능 측면에서 JSON 객체 내에서 하나의 데이터를 변경하는지 또는 모든 데이터를 변경하는지는 중요하지 않습니다. 새로운 버전의 행을 작성해야합니다.

따라서 매뉴얼조언 :

JSON 데이터는 테이블에 저장 될 때 다른 데이터 유형과 동일한 동시성 제어 고려 사항이 적용됩니다. 큰 문서를 저장하는 것이 가능하지만 모든 업데이트는 전체 행에서 행 수준 잠금을 얻습니다. 업데이트 트랜잭션 간의 잠금 경합을 줄이려면 JSON 문서를 관리 가능한 크기로 제한하는 것이 좋습니다. 이상적으로 JSON 문서는 각각 비즈니스 규칙이 지시하는 원자 데이텀을 나타내야하며 독립적으로 수정할 수있는 더 작은 데이텀으로 더 이상 세분화 될 수 없습니다.

그것의 요점은 : 수정 아무것도 JSON 개체 내부를, 당신은 칼럼에 수정 된 객체를 할당해야합니다. Postgres json는 스토리지 기능 외에도 데이터 를 구축하고 조작 할 수있는 제한된 수단을 제공합니다. 버전 9.2 이후의 모든 새로운 릴리스에서 도구의 수가 대폭 향상되었습니다. 그러나 주체는 여전히 남아 있습니다. 항상 수정 된 객체를 열에 할당해야하며 Postgres는 항상 모든 업데이트에 대해 새 행 버전을 작성합니다.

Postgres 9.3 이상의 도구로 작업하는 방법에 대한 몇 가지 기술 :

이 답변은 SO의 모든 내 다른 답변 많은 downvotes로에 대해 끌었다 함께 . 사람들은이 아이디어를 좋아하지 않는 것 같습니다. 표준화 된 디자인은 비 동적 데이터보다 우수합니다. Craig Ringer의이 훌륭한 블로그 게시물은 다음과 같이 자세히 설명합니다.


6
이 답변은 JSON 유형에만 관련되며 JSONB는 무시합니다.
fiatjaf

7
@fiatjaf :이 답변은 데이터 유형에 완벽하게 적용 json하고 jsonb모두. 두 가지 모두 JSON 데이터를 저장 jsonb하며 몇 가지 장점과 단점이있는 정규화 된 이진 형식으로 저장합니다. stackoverflow.com/a/10560761/939860 두 데이터 유형 모두 데이터베이스 내부를 많이 조작 하는 데 적합하지 않습니다 . 문서 유형이 없습니다 . 작고 구조화되지 않은 JSON 문서에는 적합합니다. 그러나 크고 중첩 된 문서는 어리석은 짓입니다.
Erwin Brandstetter

7
"Postgres 9.3의 도구로 작업하는 방법에 대한 지침"은 질문에 대한 답변으로 실제로 답변에서 1 위를 차지했습니다. 때로는 유지 관리 / 스키마 변경 등을 위해 json을 업데이트하는 것이 합리적이며 json don을 업데이트하지 않는 이유 실제로 적용되지
않음

22
이 답변은 도움이되지 않습니다. 죄송합니다. @jvous, Jimothy의 답변을 정말로 받아들이고 싶지 않습니까?
Bastian Voigt

10
자신의 의견 / 의견 / 토론을 추가하기 전에 먼저 질문에 대답하십시오.
Ppp

330

Postgresql 9.5로 업그레이드 할 수 있다면 jsonb_set다른 사람들이 언급했듯이 명령을 사용할 수 있습니다.

다음의 각 SQL 문에서 where간결성을 위해 절을 생략했습니다. 분명히 다시 추가하고 싶을 것입니다.

업데이트 이름 :

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');

태그를 추가하거나 제거하는 것이 아니라 태그를 교체하십시오.

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');

두 번째 태그 교체 (0 색인) :

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');

태그를 추가합니다 ( 999 개 미만 의 태그 가있는 한 작동합니다. 인수를 999에서 1000 이상으로 변경하면 오류가 발생합니다 . Postgres 9.5.3에서는 더 이상 발생 하지 않으며 더 큰 인덱스를 사용할 수 있음) :

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);

마지막 태그를 제거하십시오.

UPDATE test SET data = data #- '{tags,-1}'

복잡한 업데이트 (마지막 태그 삭제, 새 태그 삽입 및 이름 변경) :

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');

이러한 각 예에서 실제로 JSON 데이터의 단일 필드를 업데이트하지는 않습니다. 대신 임시 수정 버전의 데이터를 만들고 수정 된 버전을 다시 열에 할당합니다. 실제로 결과는 동일해야하지만이를 염두에두면 마지막 예와 같이 복잡한 업데이트가 더 이해하기 쉬워집니다.

복잡한 예에는 세 가지 변형과 세 가지 임시 버전이 있습니다. 먼저 마지막 태그가 제거됩니다. 그런 다음 새 태그를 추가하여 해당 버전이 변환됩니다. 다음으로, 두 번째 버전은 name필드 를 변경하여 변환됩니다 . data열의 값 이 최종 버전으로 바뀝니다.


42
OP가 요청한대로 테이블의 열을 업데이트하는 방법을 보여주는 보너스 포인트가 제공됩니다.
chadrik

1
@ chadrik : 더 복잡한 예를 추가했습니다. 요청한 내용을 정확하게 수행하지는 않지만 아이디어를 제공해야합니다. 외부 jsonb_set호출에 대한 입력은 내부 호출의 출력이며 해당 내부 호출에 대한 입력은의 결과입니다 data #- '{tags,-1}'. 즉, 마지막 태그가 제거 된 원본 데이터입니다.
Jimothy

1
@PranaySoni : 그 목적을 위해 아마도 저장 프로 시저를 사용하거나 오버 헤드가 문제가되지 않는 경우 해당 데이터를 다시 가져 와서 응용 프로그램 언어로 조작 한 다음 다시 쓰십시오. 이것은 무겁게 들리지만 명심하십시오. 내가 주신 모든 예제에서 JSON (B)의 단일 필드를 업데이트하지 않고 전체 열을 덮어 씁니다. 따라서 저장된 proc은 다르지 않습니다.
Jimothy

1
@Alex : 네, 약간의 해킹입니다. 내가 말하면 {tags,0}"배열의 첫 번째 요소"를 의미 tags하므로 해당 요소에 새로운 가치를 부여 할 수 있습니다. 배열의 기존 요소를 대체하는 대신 0 대신 큰 숫자를 사용하여 배열에 새 요소를 추가합니다. 그러나 배열에 실제로 999,999,999 개 이상의 요소가있는 경우 새 요소를 추가하는 대신 마지막 요소를 대체합니다.
Jimothy

1
필드에 null이 포함되어 있으면 어떻습니까? 작동하지 않는 것 같습니다. 예를 들어 info jsonb 필드가 null입니다. "UPDATE Organizer SET info = jsonb_set (info, '{country}', '"FRA "') 여기서 info->> 'country':: text IS NULL;"UPDATE 105 레코드를 얻지 만 어떤 DB에 변경
stackdave


18

이 문제가 발생하고 매우 빠른 수정을 원하고 (9.4.5 또는 이전 버전에 붙어있는) 사람들을 위해 여기 내가 한 일이 있습니다.

테스트 테이블 생성

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

jsonb 특성의 이름을 변경하는 명령문 업데이트

UPDATE test 
SET data = replace(data::TEXT,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

궁극적으로 허용되는 답변은 jsonb 객체의 개별 부분 (9.4.5 이하)을 수정할 수 없다는 점에서 정확합니다. 그러나 jsonb 객체를 문자열 (:: TEXT)로 캐스트 한 다음 문자열을 조작하고 jsonb 객체 (:: jsonb)로 다시 캐스트 할 수 있습니다.

두 가지 중요한 경고가 있습니다

  1. 이것은 json에서 "name"이라는 모든 속성을 대체합니다 (같은 이름을 가진 여러 속성이있는 경우)
  2. 9.5를 사용하는 경우 jsonb_set만큼 효율적이지 않습니다.

즉, jsonb 객체의 내용에 대한 스키마를 업데이트 해야하는 상황이 발생했으며 이것이 원래 포스터가 요구하는 것을 정확하게 달성하는 가장 간단한 방법이었습니다.


1
좋은 영주, 나는 두 시간 동안 jsonb에 대한 업데이트를 수행하는 방법을 찾고 있었으므로 모든 \u0000null 문자를 바꿀 수 있습니다. 예는 완전한 그림을 보여주었습니다. 감사합니다!
Joshua Robinson

3
좋아 보인다! btw 예제에서 바꿀 두 번째 인수는 콜론을 포함하고 세 번째 인수는 포함하지 않습니다. 당신의 전화는replace(data::TEXT, '"name":', '"my-other-name":')::jsonb
Davidicus

@davidicus 감사합니다! 매우 지연된 업데이트에 대해 죄송하지만 다른 사용자와 공유해 주셔서 감사합니다.
차드 카프라

12

이 질문은 postgres 9.4와 관련하여 요청되었지만이 질문에 나오는 새로운 뷰어는 postgres 9.5에서 JSONB 필드에 대한 하위 문서 만들기 / 업데이트 / 삭제 작업이 데이터베이스에서 기본적으로 확장없이 지원됨을 알고 있어야합니다. 기능.

참조 : JSONB은 연산자와 함수를 수정


7

'name'속성을 업데이트하십시오.

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;

예를 들어 'name'및 'tags'속성을 제거하려면 다음을 수행하십시오.

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;

5

Postgres 9.4에서 재귀 적으로 작동하는 작은 함수를 작성했습니다. 나는 같은 문제가 있었다 (Postgres 9.5 에서이 두통 중 일부를 해결 했음). 어쨌든 여기 기능이 있습니다 (나는 그것이 당신에게 잘 작동하기를 바랍니다) :

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

다음은 샘플 사용입니다.

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

보시다시피 깊이 분석하고 필요한 경우 값을 업데이트 / 추가하십시오.


jsonb_build_object9.5에서 소개 되었기 때문에 9.4에서는 작동하지 않습니다.
Greg

@Greg 당신이 맞습니다. 방금 확인했고 PostgreSQL 9.5를 실행하고 있습니다. 이것이 작동하는 이유입니다. 지적 해 주셔서 감사합니다-내 솔루션은 9.4에서 작동하지 않습니다.
J. Raczkiewicz

4

아마도 : UPDATE test SET data = ' "my-other-name"':: json WHERE id = 1;

데이터가 json 유형 인 내 경우와 함께 작동했습니다.


1
postgresql 9.4.5에서 저에게도 효과가있었습니다. 단일 레코드 atm을 업데이트 할 수 없도록 전체 레코드가 다시 작성됩니다.
kometen

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.