web-dev-qa-db-ja.com

クエリの連結JSON(B)列のマージ

Postgres 9.4を使用して、クエリ内の2つ(またはそれ以上)のjsonまたはjsonb列をマージする方法を探しています。例として次の表を検討してください。

  id | json1        | json2
----------------------------------------
  1   | {'a':'b'}   | {'c':'d'}
  2   | {'a1':'b2'} | {'f':{'g' : 'h'}}

クエリに次を返すことは可能ですか?

  id | json
----------------------------------------
  1   | {'a':'b', 'c':'d'}
  2   | {'a1':'b2', 'f':{'g' : 'h'}}

残念ながら、 here のように関数を定義することはできません。これは「伝統的な」クエリで可能ですか?

34
Robin

以下は、PostgreSQLでjsonオブジェクトを作成するために使用できる組み込み関数の完全なリストです。 http://www.postgresql.org/docs/9.4/static/functions-json.html

  • row_to_jsonおよびjson_objectでは、独自のキーを定義できないため、ここでは使用できません
  • json_build_objectは、オブジェクトが持つキーと値の数を事前に知っていることを期待します。これはあなたの例の場合ですが、実世界の場合はそうではありません
  • json_objectはこの問題に取り組むのに適したツールのように見えますが、値をテキストにキャストすることを余儀なくされるため、これも使用できません

まあ...わかりました、古典的な機能を使用することはできません。

いくつかの集約関数を見て、最良のものを期待しましょう... http://www.postgresql.org/docs/9.4/static/functions-aggregate.html

json_object_aggは、オブジェクトを構築する唯一の集約関数であり、この問題に取り組む唯一の機会です。ここでのコツは、json_object_agg関数をフィードする正しい方法を見つけることです。

これが私のテスト表とデータです

CREATE TABLE test (
  id    SERIAL PRIMARY KEY,
  json1 JSONB,
  json2 JSONB
);

INSERT INTO test (json1, json2) VALUES
  ('{"a":"b", "c":"d"}', '{"e":"f"}'),
  ('{"a1":"b2"}', '{"f":{"g" : "h"}}');

そして、json_objectでの試行錯誤の後、ここに、PostgreSQL 9.4でjson1とjson2をマージするために使用できるクエリがあります。

WITH all_json_key_value AS (
  SELECT id, t1.key, t1.value FROM test, jsonb_each(json1) as t1
  UNION
  SELECT id, t1.key, t1.value FROM test, jsonb_each(json2) as t1
)
SELECT id, json_object_agg(key, value) 
FROM all_json_key_value 
GROUP BY id

[〜#〜] edit [〜#〜]:PostgreSQL 9.5+の場合、以下のZubinの回答をご覧ください

31

Postgres 9.5以降では、JSONBを次のようにマージできます。

select json1 || json2;

または、JSONの場合、必要に応じてJSONBに強制します。

select json1::jsonb || json2::jsonb;

または:

select COALESCE(json1::jsonb||json2::jsonb, json1::jsonb, json2::jsonb);

(それ以外の場合、json1またはjson2は空の行を返します)

例えば:

select data || '{"foo":"bar"}'::jsonb from photos limit 1;
                               ?column?
----------------------------------------------------------------------
 {"foo": "bar", "preview_url": "https://unsplash.it/500/720/123"}

コメントでこれを指摘してくれた@MattZukowskiに感謝します。

49
Zubin

また、jsonをテキストに変換し、連結し、置換し、jsonに戻すこともできます。 Clémentの同じデータを使用して、次のことができます。

SELECT replace(
    (json1::text || json2::text), 
    '}{', 
    ', ')::json 
FROM test

また、次のようにしてすべてのjson1を単一のjsonに連結することもできます。

SELECT regexp_replace(
    array_agg((json1))::text,
    '}"(,)"{|\\| |^{"|"}$', 
    '\1', 
    'g'
)::json
FROM test
6
caiohamamura

ただし、この質問は、少し前にすでに回答されています。 json1json2が同じキーを含むとき。キーはドキュメントに2回表示されますが、 ベストプラクティス ではないようです。

したがって、uはPostgreSQL 9.5でこのjsonb_merge関数を使用できます。

CREATE OR REPLACE FUNCTION jsonb_merge(jsonb1 JSONB, jsonb2 JSONB)
    RETURNS JSONB AS $$
    DECLARE
      result JSONB;
      v RECORD;
    BEGIN
       result = (
    SELECT json_object_agg(KEY,value)
    FROM
      (SELECT jsonb_object_keys(jsonb1) AS KEY,
              1::int AS jsb,
              jsonb1 -> jsonb_object_keys(jsonb1) AS value
       UNION SELECT jsonb_object_keys(jsonb2) AS KEY,
                    2::int AS jsb,
                    jsonb2 -> jsonb_object_keys(jsonb2) AS value ) AS t1
           );
       RETURN result;
    END;
    $$ LANGUAGE plpgsql;

次のクエリは、連結されたjsonb列を返します。json2のキーは、json1のキーよりも優位です。

select id, jsonb_merge(json1, json2) from test
5
API

参考までに、誰かが9.5以上でjsonbを使用していて、重複キーなしでマージされるトップレベルの要素のみに関心がある場合、||を使用するのと同じくらい簡単です。オペレーター:

select '{"a1": "b2"}'::jsonb || '{"f":{"g" : "h"}}'::jsonb;
      ?column?           
-----------------------------
 {"a1": "b2", "f": {"g": "h"}}
(1 row)
4

この関数はネストされたjsonオブジェクトをマージします

create or replace function jsonb_merge(CurrentData jsonb,newData jsonb)
 returns jsonb
 language sql
 immutable
as $jsonb_merge_func$
 select case jsonb_typeof(CurrentData)
   when 'object' then case jsonb_typeof(newData)
     when 'object' then (
       select    jsonb_object_agg(k, case
                   when e2.v is null then e1.v
                   when e1.v is null then e2.v
                   when e1.v = e2.v then e1.v 
                   else jsonb_merge(e1.v, e2.v)
                 end)
       from      jsonb_each(CurrentData) e1(k, v)
       full join jsonb_each(newData) e2(k, v) using (k)
     )
     else newData
   end
   when 'array' then CurrentData || newData
   else newData
 end
$jsonb_merge_func$;
3
Sandeep Sinha
CREATE OR REPLACE FUNCTION jsonb_merge(pCurrentData jsonb, pMergeData jsonb, pExcludeKeys text[])
RETURNS jsonb IMMUTABLE LANGUAGE sql
AS $$
    SELECT json_object_agg(key,value)::jsonb
    FROM (
        WITH to_merge AS (
            SELECT * FROM jsonb_each(pMergeData) 
        )
        SELECT *
        FROM jsonb_each(pCurrentData)
        WHERE key NOT IN (SELECT key FROM to_merge)
     AND ( pExcludeKeys ISNULL OR key <> ALL(pExcludeKeys))
        UNION ALL
        SELECT * FROM to_merge
    ) t;
$$;

SELECT jsonb_merge( '{"a":1、 "b":9、 "c":3、 "e":5}' :: jsonb、 '{"b":2、 "d":4}': :jsonb、 '{"c"、 "e"}' :: text [])jsonbとして

1
igilfanov

||の代替としてうまく機能します再帰的なディープマージが必要な場合( here が見つかりました):

create or replace function jsonb_merge_recurse(orig jsonb, delta jsonb)
returns jsonb language sql as $$
    select
        jsonb_object_agg(
            coalesce(keyOrig, keyDelta),
            case
                when valOrig isnull then valDelta
                when valDelta isnull then valOrig
                when (jsonb_typeof(valOrig) <> 'object' or jsonb_typeof(valDelta) <> 'object') then valDelta
                else jsonb_merge_recurse(valOrig, valDelta)
            end
        )
    from jsonb_each(orig) e1(keyOrig, valOrig)
    full join jsonb_each(delta) e2(keyDelta, valDelta) on keyOrig = keyDelta
$$;
0
user1767316

2つのJSONオブジェクトのマージに問題がある場合は、これを試してください

select table.attributes::jsonb || json_build_object('foo',1,'bar',2)::jsonb FROM table where table.x='y';
0
Piyush Sharma