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?
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önnenjson_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 umzuwandelnNun ... 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
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.
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
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
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)
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$;
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
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
$$;
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';