如何在 Postgres 9.4 中对 JSONB 类型的列执行更新操作

Posted

技术标签:

【中文标题】如何在 Postgres 9.4 中对 JSONB 类型的列执行更新操作【英文标题】:How to perform update operations on columns of type JSONB in Postgres 9.4 【发布时间】:2014-12-29 11:22:15 【问题描述】:

查看 Postgres 9.4 数据类型 JSONB 的文档,对我来说如何对 JSONB 列进行更新并不是很明显。

JSONB 类型和函数的文档:

http://www.postgresql.org/docs/9.4/static/functions-json.html http://www.postgresql.org/docs/9.4/static/datatype-json.html

作为一个例子,我有这个基本的表结构:

CREATE TABLE test(id serial, data jsonb);

插入很简单,如下所示:

INSERT INTO test(data) values ('"name": "my-name", "tags": ["tag1", "tag2"]');

现在,我将如何更新“数据”列?这是无效的语法:

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

这是否记录在我错过的明显地方?谢谢。

【问题讨论】:

【参考方案1】:

如果您能够升级到 Postgresql 9.5,则可以使用 jsonb_set 命令,正如其他人所提到的那样。

为简洁起见,在以下每个 SQL 语句中,我省略了 where 子句;显然,你想把它加回来。

更新名称:

UPDATE test SET data = jsonb_set(data, 'name', '"my-other-name"');

替换标签(与添加或删除标签相反):

UPDATE test SET data = jsonb_set(data, 'tags', '["tag3", "tag4"]');

替换第二个标签(0-indexed):

UPDATE test SET data = jsonb_set(data, 'tags,1', '"tag5"');

附加一个标签(只要标签少于 999 个,这将起作用;将参数 999 更改为 1000 或更高会产生错误。在 Postgres 9.5 中似乎不再是这种情况。 3;可以使用更大的索引):

UPDATE test SET data = jsonb_set(data, 'tags,999999999', '"tag6"', true);

删除最后一个标签:

UPDATE test SET data = data #- 'tags,-1'

复杂更新(删除最后一个标签,插入新标签,改名):

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- 'tags,-1', 'tags,999999999', '"tag3"', true), 
    'name', '"my-other-name"');

请务必注意,在每个示例中,您实际上并未更新 JSON 数据的单个字段。相反,您正在创建数据的临时修改版本,并将修改后的版本分配回列。在实践中,结果应该是相同的,但牢记这一点应该使复杂的更新(如上一个示例)更易于理解。

在复杂示例中,有三个转换和三个临时版本:首先,删除最后一个标签。然后,通过添加新标签来转换该版本。接下来,通过更改name 字段来转换第二个版本。 data 列中的值被替换为最终版本。

【讨论】:

您可以根据 OP 的要求展示如何更新表格中的列,从而获得奖励积分 @chadrik:我添加了一个更复杂的示例。它并没有完全按照您的要求做,但它应该给您一个想法。请注意,外部jsonb_set 调用的输入是内部调用的输出,而该内部调用的输入是data #- 'tags,-1' 的结果。即去掉最后一个标签的原始数据。 @PranaySoni:为此,我可能会使用存储过程,或者,如果开销不是问题,则将数据带回,以应用程序的语言对其进行操作,然后将其写回.这听起来很沉重,但请记住,在我给出的所有示例中,您仍然没有更新 JSON(B) 中的单个字段:无论哪种方式,您都在覆盖整个列。所以存储过程真的没有什么不同。 @Alex:是的,有点小技巧。如果我说tags,0,那将意味着“数组tags 的第一个元素”,允许我为该元素赋予一个新值。通过使用大数而不是 0,而不是替换数组中的现有元素,它向数组中添加了一个新元素。但是,如果数组中实际上有超过 999,999,999 个元素,这将替换最后一个元素而不是添加一个新元素。 如果字段包含 null 怎么办?看起来不工作。例如 info jsonb 字段为空:“UPDATE Organizer SET info = jsonb_set(info, 'country', '"FRA"') where info->>'country'::text IS NULL;" 我得到 UPDATE 105 记录但是数据库没有变化【参考方案2】:

理想情况下,您不要将 JSON 文档用于要在关系数据库中操作的结构化常规数据。请改用规范化的关系设计

JSON 主要用于存储不需要在 RDBMS 中操作的整个文档。相关:

JSONB with indexing vs. hstore

在 Postgres 中更新一行总是写入 整个 行的新版本。这就是Postgres' MVCC model的基本原理。从性能的角度来看,您是更改 JSON 对象中的单个数据还是更改所有数据都无关紧要:必须编写行的新版本。

因此advice in the manual:

JSON 数据受制于相同的并发控制注意事项 存储在表中时的任何其他数据类型。虽然存储大 文件是可行的,请记住,任何更新都需要 整行的行级锁。考虑将 JSON 文档限制为 可管理的大小,以减少更新之间的锁争用 交易。理想情况下,每个 JSON 文档都应该代表一个原子 业务规则规定的数据不能合理地进一步 细分为可以独立修改的较小基准。

要点:要修改 JSON 对象内的 任何内容,您必须将修改后的对象分配给列。除了存储能力之外,Postgres 还提供了有限的方法来构建和操作json 数据。自 9.2 版以来,随着每个新版本的发布,工具库都大幅增加。但原则仍然存在:您总是必须为列分配一个完整的修改对象,而 Postgres 总是为任何更新写入一个新的行版本。

如何使用 Postgres 9.3 或更高版本的工具的一些技巧:

How do I modify fields inside the new PostgreSQL JSON datatype?

这个答案吸引了与我在 SOtogether 上的所有其他答案一样多的反对票。人们似乎不喜欢这个想法:规范化设计对于常规数据来说更胜一筹。 Craig Ringer 的这篇出色的博客文章更详细地解释了:

"PostgreSQL anti-patterns: Unnecessary json/hstore dynamic columns"

Laurenz Albe 的另一篇博文,另一个 official Postgres contributor 像 Craig 和我自己:

JSON in PostgreSQL: how to use it right

【讨论】:

这个答案只关注 JSON 类型,忽略 JSONB。 @fiatjaf:此答案完全适用于 jsonjsonb 等数据类型。两者都存储 JSON 数据,jsonb 以标准化的二进制形式存储,具有一些优点(和一些缺点)。 ***.com/a/10560761/939860 这两种数据类型都不适合在数据库中进行大量操作No 文档类型是。好吧,它适用于小型、几乎没有结构化的 JSON 文档。但是那样的话,大的、嵌套的文档会很愚蠢。 “如何使用 Postgres 9.3 工具的说明”确实应该在您的回答中排在第一位,因为它回答了所提出的问题。有时更新 json 以进行维护/架构更改等是有意义的不更新 json 的原因并不真正适用 在添加您自己的评论/意见/讨论之前先回答问题。 @taleodor:每个版本都改进了 JSON 支持,现在已经非常出色了。已经有一段时间了。对于某些应用程序非常有用。但我的回答仍然完全适用——尤其是对于这个问题所问的“更新操作”——因为它解决了文档类型的原则限制。对于常规数据,或多或少标准化的数据库模式中的适当列通常更加更有效。这不会改变。 Postgres 项目提供了相应的建议,就像我在上面引用的那样——在 Postgres 13 开发手册之前没有改变。【参考方案3】:

这是在 9.5 中以 jsonb_set 的形式出现的 Andrew Dunstan 基于适用于 9.4 的现有扩展 jsonbx

【讨论】:

这一行的另一个问题,是使用jsonb_build_object(),因为x->key,不返回键对象对,填充你需要jsonb_set(target, path, jsonb_build_object('key',x->key))【参考方案4】:

对于遇到此问题并希望快速修复(并且卡在 9.4.5 或更早版本)的用户,这里有一个潜在的解决方案:

创建测试表

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('"name": "my-name", "tags": ["tag1", "tag2"]');

更新语句以更改 jsonb 值

UPDATE test 
SET data = replace(data::TEXT,': "my-name"',': "my-other-name"')::jsonb 
WHERE id = 1;

最终,接受的答案是正确的,因为您不能修改 jsonb 对象的单个部分(在 9.4.5 或更早版本中);但是,您可以将 jsonb 列转换为字符串 (::TEXT),然后操作该字符串并转换回 jsonb 形式 (::jsonb)。

有两个重要的警告

    这将替换 json 中所有等于“my-name”的值(如果您有多个具有相同值的对象) 这不如使用 9.5 的 jsonb_set 高效

【讨论】:

天哪,我一直在寻找如何更新 jsonb 两个小时,以便我可以替换所有 \u0000 空字符,示例显示了完整的图片。谢谢! 看起来不错!顺便说一句,您的示例中要替换的第二个参数包括冒号,而第三个不包括。看起来你的电话应该是replace(data::TEXT, '"name":', '"my-other-name":')::jsonb 谢谢@davidicus!很抱歉更新非常延迟,但感谢您为他人分享! 如果您走这条路,请非常小心地清理您的用户输入,以免他们污染您的数据。【参考方案5】:

更新“名称”属性:

UPDATE test SET data=data||'"name":"my-other-name"' WHERE id = 1;

如果您想删除例如“名称”和“标签”属性:

UPDATE test SET data=data-'"name","tags"'::text[] WHERE id = 1;

【讨论】:

【参考方案6】:

这个问题是在 postgres 9.4 的上下文中提出的, 然而,来到这个问题的新观众应该知道,在 postgres 9.5 中, 数据库原生支持对 JSONB 字段的子文档创建/更新/删除操作,无需扩展功能。

见:JSONB modifying operators and functions

【讨论】:

【参考方案7】:

我为自己编写了一个在 Postgres 9.4 中递归工作的小函数。我有同样的问题(很好,他们确实在 Postgres 9.5 中解决了一些令人头疼的问题)。 无论如何,这里是函数(我希望它对你有用):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

这里是使用示例:

select jsonb_update('"a":"b":"c":"d":5,"dd":6,"cc":1,"aaa":5'::jsonb, '"a":"b":"c":"d":15,"aa":9'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 "a": "b": "c": "d": 15, "dd": 6, "cc": 1, "aa": 9, "aaa": 5
(1 row)

如您所见,它会深入分析并在需要时更新/添加值。

【讨论】:

这在 9.4 中不起作用,因为 jsonb_build_object 是在 9.5 中引入的 @Greg 你是对的,我刚刚检查过,我现在正在运行 PostgreSQL 9.5 - 这就是它工作的原因。感谢您指出这一点 - 我的解决方案在 9.4 中不起作用。 @J.Raczkiewicz 函数很好用!如果值不存在,我如何增强您的功能以添加插入?这在空列值的情况下是必需的(例如,还没有 的空列)类似于 jsonb_set 函数中的 create if missing boolean。 jsonb_set ( target jsonb, path text[], new_value jsonb [, create_if_missing boolean ] )postgresql.org/docs/13/functions-json.html.【参考方案8】:

也许: UPDATE test SET data = '"my-other-name"'::json WHERE id = 1;

它适用于我的情况,其中数据是 json 类型

【讨论】:

在 postgresql 9.4.5 上也为我工作。整条记录都被重写,因此无法更新单个字段 atm。【参考方案9】:

Matheus de Oliveira 在 postgresql 中为 JSON CRUD 操作创建了方便的函数。它们可以使用 \i 指令导入。如果您的数据类型为 jsonb,请注意函数的 jsonb 分支。

9.3 jsonhttps://gist.github.com/matheusoliveira/9488951

9.4 jsonb https://gist.github.com/inindev/2219dff96851928c2282

【讨论】:

以上是关于如何在 Postgres 9.4 中对 JSONB 类型的列执行更新操作的主要内容,如果未能解决你的问题,请参考以下文章

如何在 postgres 中对 jsonb 值求和?

使用 postgres 9.4 将 JSON 元素附加到数组

在 Postgres JSON 数组中查询

Postgresql 9.4 - 转换为 JSONB 时输入语法无效

如何使用 Slick 在 Postgres 中将 json 对象插入到 jsonb 类型的列中

如何仅从 postgres 获取特定键的 jsonb?