如何合并两个 JSON 数组中的记录?

How can I merge records inside two JSON arrays?

我有两个 Postgres SQL 查询返回 JSON 数组:

第一季度:

[
  {"id": 1, "a": "text1a", "b": "text1b"},
  {"id": 2, "a": "text2a", "b": "text2b"},
  {"id": 2, "a": "text3a", "b": "text3b"},
  ...
 ]

q2:

[
  {"id": 1, "percent": 12.50}, 
  {"id": 2, "percent": 75.00}, 
  {"id": 3, "percent": 12.50}
  ...
]

我希望结果是两个数组唯一元素的并集:

[
  {"id": 1, "a": "text1a", "b": "text1b", "percent": 12.50},
  {"id": 2, "a": "text2a", "b": "text2b", "percent": 75.00},
  {"id": 3, "a": "text3a", "b": "text3b", "percent": 12.50},
  ...
]

如何使用 Postgres 9.4 中的 SQL 完成此操作?

假设数据类型jsonb并且您想要合并共享相同'id'值的每个JSON数组的记录。

Postgres 9.5

使用新的 concatenate operator || for jsonb values 使其更简单:

SELECT json_agg(elem1 || elem2) AS result
FROM  (
   SELECT elem1->>'id' AS id, elem1
   FROM  (
      SELECT '[
        {"id":1, "percent":12.50}, 
        {"id":2, "percent":75.00}, 
        {"id":3, "percent":12.50}
       ]'::jsonb AS js
      ) t, jsonb_array_elements(t.js) elem1
   ) t1
FULL JOIN (
   SELECT elem2->>'id' AS id, elem2
   FROM  (
      SELECT '[
        {"id": 1, "a": "text1a", "b": "text1b", "percent":12.50},
        {"id": 2, "a": "text2a", "b": "text2b", "percent":75.00},
        {"id": 3, "a": "text3a", "b": "text3b", "percent":12.50}]'::jsonb AS js
      ) t, jsonb_array_elements(t.js) elem2
   ) t2 USING (id);

FULL [OUTER] JOIN 确保您不会丢失其他数组中不匹配的记录。

类型jsonb有方便的属性只保留记录中每个键的最新值。因此,结果中重复的 'id' 键会自动合并。

Postgres 9.5 手册还建议:

Note: The || operator concatenates the elements at the top level of each of its operands. It does not operate recursively. For example, if both operands are objects with a common key field name, the value of the field in the result will just be the value from the right hand operand.

Postgres 9.4

有点不方便。我的想法是提取数组元素,然后提取所有 key/value 对,UNION 两个结果,每个 id 值聚合成一个新的 jsonb 值,最后聚合成一个数组。

SELECT json_agg(j) -- ::jsonb
FROM  (
   SELECT json_object_agg(key, value)::jsonb AS j
   FROM  (
      SELECT elem->>'id' AS id, x.*
      FROM  (
         SELECT '[
           {"id":1, "percent":12.50}, 
           {"id":2, "percent":75.00}, 
           {"id":3, "percent":12.50}]'::jsonb AS js
         ) t, jsonb_array_elements(t.js) elem, jsonb_each(elem) x
      UNION ALL  -- or UNION, see below
      SELECT elem->>'id' AS id, x.*
      FROM  (
         SELECT '[
           {"id": 1, "a": "text1a", "b": "text1b", "percent":12.50},
           {"id": 2, "a": "text2a", "b": "text2b", "percent":75.00},
           {"id": 3, "a": "text3a", "b": "text3b", "percent":12.50}]'::jsonb AS js
         ) t, jsonb_array_elements(t.js) elem, jsonb_each(elem) x
      ) t
   GROUP  BY id
   ) t;

jsonb 的强制转换删除了重复键。或者,您可以使用 UNION 折叠重复项(例如,如果您想要 json 作为结果)。测试哪个对你的情况来说更快。

相关:

对于任何单个 jsonb 元素,concat || 运算符的使用对我来说效果很好 strip_nulls 和另一个将结果转换回 jsonb(不是数组)的技巧。

select jsonb_array_elements(jsonb_strip_nulls(jsonb_agg(
    '{
        "a" : "unchanged value",
        "b" : "old value",
        "d" : "delete me"
    }'::jsonb
    || -- The concat operator works as merge on jsonb, the right operand takes precedence
    -- NOTE: it only works one JSON level deep
    '{
        "b" : "NEW value",
        "c" : "NEW field",
        "d" : null
    }'::jsonb
)));

这给出了结果

 {"a": "unchanged value", "b": "NEW value", "c": "NEW field"}

输入正确jsonb