在查询中合并连接 JSON(B) 列

Posted

技术标签:

【中文标题】在查询中合并连接 JSON(B) 列【英文标题】:Merging Concatenating JSON(B) columns in query 【发布时间】:2015-07-18 01:23:03 【问题描述】:

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

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

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

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

很遗憾,我无法定义 here 中描述的函数。这可以通过“传统”查询实现吗?

【问题讨论】:

您使用的是哪个版本的 postgres? @ClémentPrévost 我使用 postgres 9.4 如果json1json2 中也有一个键/值对,你想发生什么?正确的先例,还是合并? 【参考方案1】:

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

select json1 || json2;

或者,如果是 JSON,必要时强制转换为 JSONB:

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

或者:

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

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

例如:

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

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

【讨论】:

这实际上并没有进行深度合并。SELECT '"a":[1,2,3]'::jsonb || '"a":[4,5,6]'::jsonb; rhs 中的键/V 对破坏了 LHS。 美丽。我喜欢 Postgres! @EvanCarroll JSONB 不允许重复密钥。所以,它不能进行深度合并。 @JianHe 当然可以,而且会崩溃。这在规范中是允许的。 ***.com/a/23195243/124486试试看select '"foo":42'::jsonb || '"foo":20'::jsonb; @EvanCarroll。现在我在 13.2,Windows 版本。选择'"foo":42'::jsonb || '"foo":20'::jsonb;返回 ` "foo": 20 ` 我以为你想要 "foo": [20,42]【参考方案2】:

这里是可用于在 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 函数。

这是我的测试表和数据

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

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's answer。

【讨论】:

【参考方案3】:

这个函数会合并嵌套的json对象

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$;

【讨论】:

欢迎来到 Stack Overflow!感谢您提供此代码 sn-p,它可能会提供一些有限的短期帮助。一个正确的解释would greatly improve 它的长期价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。 这是我的 2 美分:我稍作修改,只允许嵌套数组中的唯一值,只需将数组部分中的 CurrentData || newData 替换为 array_to_json(array(select distinct jsonb_array_elements( CurrentData || newData)))::jsonbTODO: 不要使用 JSON 作为中间体而不是 JOSNB,为数组找到正确的函数【参考方案4】:

您还可以将 json 转换为文本、连接、替换和转换回 json。使用来自 Clément 的相同数据,您可以这样做:

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

您还可以使用以下方法将所有 json1 连接成单个 json:

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

这是一个非常古老的解决方案,从 9.4 开始,您应该使用 json_object_agg 和简单的 || 连接运算符。留在这里仅供参考。

【讨论】:

在 Postgres 9.5 中,假设您的列是 JSONB,您只需执行 SELECT json1 || json2 即可合并 JSON 值。 这种方法可能会导致重复的键,例如"a":1 + "a":2 = "a":1, "a":2 我没看到:选择 '"a":1'::jsonb || '"a":2'::jsonb = "a": 2 这种方法允许保持json中项目的顺序,在其他情况下连接jsonb看起来很不错 我使用的是旧版本的 postgres,而 replace(old_value::text, '', ', "new_key": "new_value"')::jsonb 是唯一有效的方法。谢谢!【参考方案5】:

然而,这个问题已经在一段时间前得到了回答;当json1json2 包含相同的密钥时; key 在文档中出现两次,好像不是best practice。

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

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 中的键占主导地位:

select id, jsonb_merge(json1, json2) from test

【讨论】:

不适用于 PostgreSQL 9.4.4:SELECT jsonb_merge('"11111":1, "2":2'::jsonb, '"11111":3': :jsonb) 在 9.5 上测试 - 运行良好。要添加优先级,只需在函数中将 UNION 替换为 UNION ALL。 @Dmitry 它根本不起作用。 select jsonb_merge('"a":"nested":1'::jsonb, '"a":"also nested":2'::jsonb); 应该导致 a 具有 2 个属性,但它没有。 @Phill;你是对的,但你可以通过添加一个 jsonb_object_keys 子层来解决这个问题,该子层由具有另一个聚合函数的键分组——如果子值是一个 jsonb 对象。这可以重复,直到您合并 subsubsublayer...等 @API 我创建了一个带有 javascript 合并功能的新版本:gist.github.com/phillip-haydon/54871b746201793990a18717af8d70dc 我还没有整理它。处理我工作中的示例文档。【参考方案6】:

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

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

【讨论】:

【参考方案7】:

似乎还没有人提出这种解决方案,所以这是我的看法,使用custom aggregate functions:

create or replace aggregate jsonb_merge_agg(jsonb)
(
    sfunc = jsonb_concat,
    stype = jsonb,
    initcond = ''
);

create or replace function jsonb_concat(a jsonb, b jsonb) returns jsonb
    as 'select $1 || $2'
    language sql
    immutable
    parallel safe
;

注意:这是使用 || 替换同一路径中的现有值,而不是深度合并它们。

现在jsonb_merge_agg 可以像这样访问:

select jsonb_merge_agg(some_col) from some_table group by something;

【讨论】:

【参考方案8】:

如果有人在合并两个 JSON 对象时遇到问题,试试这个

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

【讨论】:

【参考方案9】:
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[]) as jsonb

【讨论】:

【参考方案10】:

作为 || 的替代品效果很好当需要递归深度合并时(找到here):

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(B) 列的主要内容,如果未能解决你的问题,请参考以下文章

SQL多表查询

sql 如何使几个子查询的结果用一列连接起来显示(试过合并、左右连接好像都不行),求大神指导。

Bash - 按列连接(合并)文件

小贝_mysql select连接查询

sql 用union合并合并查询结果

多表合并且去掉重复项