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に関する他のすべての回答と同じくらい多くの反対票を集めまし。人々はこのアイデアを好まないようです。正規化されたデザインは、非動的データよりも優れています。Craig Ringerによるこの優れたブログ投稿では、さらに詳しく説明しています。


6
この回答はJSONタイプにのみ関係し、JSONBを無視します。
fiatjaf 2015

7
@fiatjaf:この答えは、データ型jsonなどに完全に適用できjsonbます。どちらもJSONデータを格納し、jsonbいくつかの利点(およびいくつかの欠点)を持つ正規化されたバイナリ形式で保存します。stackoverflow.com/a/10560761/939860どちらのデータ型も、データベース内で多くの操作を行うには適していません。ドキュメントタイプはありません。まあ、それは小さな、ほとんど構造化されていないJSONドキュメントには問題ありません。しかし、大きなネストされたドキュメントは、そのように愚かになります。
Erwin Brandstetter、2015

7
「Postgres 9.3のツールを使用する方法の説明」は、質問の回答に回答するため、回答の最初にあるようです。メンテナンス/スキーマの変更などのためにjsonを更新することは理にかなっています。jsonの更新を行わない理由本当に
当てはまら

22
この回答は役に立ちません。申し訳ありません。@jvous、ジモシーの答えを受け入れたくありませんか?
Bastian Voigt

10
独自のコメント/意見/議論を追加する前に、まず質問に答えてください。
Ppp、

330

jsonb_set他の人が述べたように、Postgresql 9.5にアップグレードできる場合は、コマンドを使用できます。

次の各SQLステートメントではwhere、簡潔にするために節を省略しています。明らかに、あなたはそれを元に戻したいでしょう。

更新名:

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

タグを置き換える(タグの追加または削除とは対照的に):

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

2番目のタグ(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データの単一のフィールドを更新しているわけではないことに注意することが重要です。代わりに、データの一時的な変更バージョンを作成し、その変更バージョンを列に割り当てます。実際には同じ結果になるはずですが、これを念頭に置いておくと、前の例のような複雑な更新がより理解しやすくなります。

複雑な例では、3つの変換と3つの一時バージョンがあります。最初に、最後のタグが削除されます。次に、新しいタグを追加することにより、そのバージョンが変換されます。次に、nameフィールドを変更することにより、2番目のバージョンが変換されます。data列の値は最終バージョンに置き換えられます。


42
OPの要求に応じてテーブルの列を更新する方法を示すボーナスポイントを獲得
chadrik

1
@chadrik:より複雑な例を追加しました。それはあなたが要求したものを正確に実行しませんが、それはあなたにアイデアを与えるはずです。外部jsonb_set呼び出しへの入力は内部呼び出しからの出力であり、その内部呼び出しへの入力はの結果であることに注意してくださいdata #- '{tags,-1}'。つまり、最後のタグが削除された元のデータ。
Jimothy、2016

1
@PranaySoni:そのために、おそらくストアドプロシージャを使用するか、オーバーヘッドが問題にならない場合は、そのデータを元に戻し、アプリケーションの言語で操作してから、書き戻します。これは重く聞こえますが、私が示したすべての例では、JSON(B)の単一のフィールドをまだ更新していないことに注意してください。列全体をいずれかの方法で上書きしています。したがって、ストアドプロシージャは実際には違いはありません。
ジモシー2016

1
@アレックス:はい、少しハック。私が言った場合{tags,0}、それは「配列の最初の要素」を意味し、その要素にtags新しい値を与えることができます。0の代わりに大きな数を使用することにより、配列内の既存の要素を置き換える代わりに、新しい要素を配列に追加します。ただし、配列に実際に999,999,999を超える要素が含まれている場合は、新しい要素を追加するのではなく、最後の要素を置き換えます。
Jimothy 2016年

1
フィールドにnullが含まれている場合はどうなりますか?動作しないように見えます。たとえば、info jsonbフィールドがnullの場合: "UPDATEオーガナイザーSET info = jsonb_set(info、 '{country}'、 '" FRA "')where 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)にキャストすることができます。

重要な警告が2つあります

  1. これは、jsonで「名前」と呼ばれるすべてのプロパティを置き換えます(同じ名前のプロパティが複数ある場合)
  2. これは、9.5を使用している場合のjsonb_setほど効率的ではありません。

そうは言っても、jsonbオブジェクトのコンテンツのスキーマを更新しなければならない状況に出くわしました。これが、元の投稿者が求めていることを正確に実行する最も簡単な方法でした。


1
良い主よ、私はすべての\u0000null文字を置き換えることができるように、jsonbの更新を2時間ほど行う方法を探していました。例では、全体像を示しています。これをありがとう!
ジョシュアロビンソン

3
いいね!ところで、例で置き換える2番目の引数にはコロンが含まれ、3番目には含まれていません。お電話のようですreplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus 2017年

@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 2015年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.