PostgreSQL для сравнения двух объектов типа JSONB
СPostgreSQL(v9.5) , форматыJSONB предоставляют потрясающие возможности. Но теперь я застрял с тем, что кажется относительно простой операцией;
Сравните два объекта jsonb; посмотрите, что отличается или отсутствует в одном документе по сравнению с другим.
Что у меня есть до сих пор
WITH reports(id,DATA) AS (
VALUES (1,'{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb),
(2,'{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb) )
SELECT jsonb_object_agg(anon_1.key, anon_1.value)
FROM
(SELECT anon_2.key AS KEY,
reports.data -> anon_2.KEY AS value
FROM reports,
(SELECT DISTINCT jsonb_object_keys(reports.data) AS KEY
FROM reports) AS anon_2
ORDER BY reports.id DESC) AS anon_1
Должно возвращать разницу строки 1 по сравнению с строкой 2:
'{"b":"bbb", "c":"ccc", "d":null}'
Вместо этого он возвращает также дубликаты ({"a": "aaa"}
). Кроме того, возможно, есть более элегантный подход вообще!
3 ответа:
Обновлено
CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB) RETURNS JSONB AS $$ DECLARE result JSONB; v RECORD; BEGIN result = val1; FOR v IN SELECT * FROM jsonb_each(val2) LOOP IF result @> jsonb_build_object(v.key,v.value) THEN result = result - v.key; ELSIF result ? v.key THEN CONTINUE; ELSE result = result || jsonb_build_object(v.key,'null'); END IF; END LOOP; RETURN result; END; $$ LANGUAGE plpgsql;
Запрос:
SELECT jsonb_diff_val( '{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb, '{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb ); jsonb_diff_val --------------------------------------- {"b": "bbb", "c": "ccc", "d": "null"} (1 row)
Я создал аналогичную функцию, которая будет сканировать объект рекурсивно и будет возвращать разницу между новым объектом и старым объектом. Я не смог найти "более приятный" способ определить, является ли объект jsonb "пустым", поэтому был бы благодарен за любое предложение, как это упростить. Я планирую использовать его для отслеживания обновлений, сделанных для объектов jsonb, поэтому я сохраняю только то, что изменилось.
Вот функция:
CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB) RETURNS JSONB AS $$ DECLARE result JSONB; object_result JSONB; i int; v RECORD; BEGIN IF jsonb_typeof(val1) = 'null' THEN RETURN val2; END IF; result = val1; FOR v IN SELECT * FROM jsonb_each(val1) LOOP result = result || jsonb_build_object(v.key, null); END LOOP; FOR v IN SELECT * FROM jsonb_each(val2) LOOP IF jsonb_typeof(val1->v.key) = 'object' AND jsonb_typeof(val2->v.key) = 'object' THEN object_result = jsonb_diff_val(val1->v.key, val2->v.key); -- check if result is not empty i := (SELECT count(*) FROM jsonb_each(object_result)); IF i = 0 THEN result = result - v.key; --if empty remove ELSE result = result || jsonb_build_object(v.key,object_result); END IF; ELSIF val1->v.key = val2->v.key THEN result = result - v.key; ELSE result = result || jsonb_build_object(v.key,v.value); END IF; END LOOP; RETURN result; END; $$ LANGUAGE plpgsql;
Тогда простой запрос выглядит так:
SELECT jsonb_diff_val( '{"a":"aaa", "b":{"b1":"b","b2":"bb","b3":{"b3a":"aaa","b3c":"ccc"}}, "c":"ccc"}'::jsonb, '{"a":"aaa", "b":{"b1":"b1","b3":{"b3a":"aaa","b3c":"cccc"}}, "d":"ddd"}'::jsonb ); jsonb_diff_val ------------------------------------------------------------------------------- {"b": {"b1": "b1", "b2": null, "b3": {"b3c": "cccc"}}, "c": null, "d": "ddd"} (1 row)
Мое решение не является рекурсивным, но вы можете использовать его для обнаружения общего ключа / значений:
-- Diff two jsonb objects CREATE TYPE jsonb_object_diff_result AS ( old jsonb, new jsonb, same jsonb ); CREATE OR REPLACE FUNCTION jsonb_object_diff(in_old jsonb, in_new jsonb) RETURNS jsonb_object_diff_result AS $jsonb_object_diff$ DECLARE _key text; _value jsonb; _old jsonb; _new jsonb; _same jsonb; BEGIN _old := in_old; _new := in_new; FOR _key, _value IN SELECT * FROM jsonb_each(_old) LOOP IF (_new -> _key) = _value THEN _old := _old - _key; _new := _new - _key; IF _same IS NULL THEN _same := jsonb_build_object(_key, _value); ELSE _same := _same || jsonb_build_object(_key, _value); END IF; END IF; END LOOP; RETURN (_old, _new, _same); END; $jsonb_object_diff$ LANGUAGE plpgsql;
Результат может выглядеть так:
SELECT * FROM jsonb_object_diff( '{"a": 1, "b": 5, "extra1": "woo", "old_null": null, "just_null": null}'::jsonb, '{"a": 1, "b": 4, "extra2": "ahoj", "new_null": null, "just_null": null}'::jsonb); -[ RECORD 1 ]-------------------------------------- old | {"b": 5, "extra1": "woo", "old_null": null} new | {"b": 4, "extra2": "ahoj", "new_null": null} same | {"a": 1, "just_null": null}