关于postgresql:在查询中合并连接JSON(B)列

Merging Concatenating JSON(B) columns in query

使用Postgres 9.4,我正在寻找一种方法来合并查询中的两个(或更多)jsonjsonb列。 以下表为例:

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

是否可以让查询返回以下内容:

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

不幸的是,我无法定义此处所述的功能。 这可能是"传统"查询吗?


在Postgres 9.5+中你可以像这样合并JSONB:

1
SELECT json1 || json2;

或者,如果它是JSON,则在必要时强制使用JSONB:

1
SELECT json1::jsonb || json2::jsonb;

要么:

1
SELECT COALESCE(json1::jsonb||json2::jsonb, json1::jsonb, json2::jsonb);

(否则,json1json2中的任何空值都会返回一个空行)

例如:

1
2
3
4
SELECT DATA || '{"foo":"bar"}'::jsonb FROM photos LIMIT 1;
                               ?COLUMN?
----------------------------------------------------------------------
 {"foo":"bar","preview_url":"https://unsplash.it/500/720/123"}

感谢@MattZukowski在评论中指出这一点。


以下是可用于在PostgreSQL中创建json对象的内置函数的完整列表。 http://www.postgresql.org/docs/9.4/static/functions-json.html

  • row_to_jsonjson_object不允许您定义自己的密钥,因此不能在此处使用
  • json_build_object希望您提前了解我们的对象将拥有多少个键和值,在您的示例中就是这种情况,但在现实世界中不应该是这种情况
  • json_object看起来像是解决这个问题的好工具,但它迫使我们将我们的值转换为文本,所以我们不能使用这个

嗯......好吧,我们不能使用任何经典功能。

让我们来看看一些集合函数,希望最好... http://www.postgresql.org/docs/9.4/static/functions-aggregate.html

json_object_agg是构建对象的唯一聚合函数,这是我们解决此问题的唯一机会。这里的技巧是找到提供json_object_agg功能的正确方法。

这是我的测试表和数据

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
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

编辑:对于PostgreSQL 9.5+,请看下面的Zubin的答案


您还可以将json转换为文本,连接,替换并转换回json。使用Clément提供的相同数据,您可以:

1
2
3
4
5
SELECT REPLACE(
    (json1::text || json2::text),
    '}{',
    ', ')::json
FROM test

您还可以将所有json1连接成单个json:

1
2
3
4
5
6
7
SELECT regexp_replace(
    array_agg((json1))::text,
    '}"(,)"{|\\| |^{"|"}$',
    '\1',
    'g'
)::json
FROM test


但是这个问题已经在前一段时间得到了回答;当json1json2包含相同的键时;密钥在文档中出现两次,似乎不是最佳实践。

因此你可以在PostgreSQL 9.5中使用这个jsonb_merge函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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中键的占优势:

1
SELECT id, jsonb_merge(json1, json2) FROM test


仅供参考,如果有人在> = 9.5时使用jsonb并且他们只关心没有重复键合并的顶层元素,那么它就像使用||一样简单运营商:

1
2
3
4
5
SELECT '{"a1":"b2"}'::jsonb || '{"f":{"g" :"h"}}'::jsonb;
      ?COLUMN?          
-----------------------------
 {"a1":"b2","f": {"g":"h"}}
(1 ROW)

此函数将合并嵌套的json对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
$$;

试试这个,如果有人有合并两个JSON对象的问题

1
SELECT TABLE.attributes::jsonb || json_build_object('foo',1,'bar',2)::jsonb FROM TABLE WHERE TABLE.x='y';