it-swarm.com.de

Zusammenführen von verketteten JSON (B) -Spalten in der Abfrage

Mit Postgres 9.4 suche ich nach einer Möglichkeit, zwei (oder mehr) json- oder jsonb-Spalten in einer Abfrage zusammenzuführen. Betrachten Sie die folgende Tabelle als Beispiel:

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

Ist es möglich, dass die Abfrage Folgendes zurückgibt:

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

Leider kann ich eine Funktion nicht wie hier beschrieben definieren hier . Ist das mit einer "traditionellen" Abfrage möglich?

29
Robin

Hier ist die vollständige Liste der integrierten Funktionen, die zum Erstellen von Json-Objekten in PostgreSQL verwendet werden können. http://www.postgresql.org/docs/9.4/static/functions-json.html

  • row_to_json und json_object erlauben Ihnen nicht, Ihre eigenen Schlüssel zu definieren, so dass sie hier nicht verwendet werden können
  • json_build_object erwartet, dass Sie im Voraus wissen, wie viele Schlüssel und Werte unser Objekt haben wird. Dies ist in Ihrem Beispiel der Fall, sollte aber in der realen Welt nicht der Fall sein 
  • json_object scheint ein gutes Werkzeug zu sein, um dieses Problem anzugehen, aber es zwingt uns, unsere Werte in Text umzuwandeln

Nun ... ok, wir können keine klassischen Funktionen verwenden.

Schauen wir uns einige Aggregatfunktionen an und hoffen auf das Beste ... http://www.postgresql.org/docs/9.4/static/functions-aggregate.html

json_object_agg Ist die einzige Aggregatfunktion zum Erstellen von Objekten. Dies ist unsere einzige Chance, dieses Problem zu lösen. Der Trick hier ist, den richtigen Weg zu finden, um die json_object_agg-Funktion einzugeben.

Hier sind meine Testtabelle und Daten

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"}}');

Und nach einigen Versuchen und Fehlern mit json_object ist hier eine Abfrage, mit der Sie json1 und json2 in PostgreSQL 9.4 zusammenführen können

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: Für PostgreSQL 9.5+ siehe Zubins Antwort unten

25

In Postgres 9.5+ können Sie JSONB wie folgt zusammenführen:

select json1 || json2;

Oder, wenn es sich um JSON handelt, zwingen Sie ggf. JSONB:

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

Oder:

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

(Andernfalls gibt jeder Nullwert in json1 oder json2 eine leere Zeile zurück.)

Zum Beispiel:

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

Ein großes Lob an @MattZukowski für den Hinweis in einem Kommentar.

39
Zubin

Sie können auch Json in Text umwandeln, verketten, ersetzen und wieder in Json konvertieren. Mit den gleichen Daten von Clément können Sie Folgendes tun:

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

Sie können auch alle Json1 zu Single-Json verketten:

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

Diese Frage wird jedoch schon vor einiger Zeit beantwortet; die Tatsache, dass, wenn json1 und json2 den gleichen Schlüssel enthalten; Der Schlüssel erscheint zweimal im Dokument, scheint jedoch nicht best practice zu sein.

Daher können Sie diese jsonb_merge-Funktion mit PostgreSQL 9.5 verwenden:

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;

Die folgende Abfrage gibt die verketteten Jsonb-Spalten zurück, wobei die Schlüssel in json2 die Schlüssel in json1 dominieren:

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

Zu Ihrer Information, wenn jemand jsonb in> = 9.5 verwendet und er sich nur darum kümmert, dass Elemente der obersten Ebene ohne doppelte Schlüssel zusammengeführt werden, ist dies genauso einfach wie das Verwenden der || Operator:

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

Diese Funktion würde verschachtelte Json-Objekte zusammenführen 

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$;
1
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 []) als jsonb

0
igilfanov

funktioniert gut als Alternative zu || wenn rekursives Deep Merge erforderlich ist (gefunden hier ):

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

Versuchen Sie dies, wenn jemand ein Problem beim Zusammenführen von zwei JSON-Objekten hat

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