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


132

查看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;

这是我错过的明显地方吗?谢谢。

Answers:


32

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

JSON主要用于存储不需要在RDBMS内部进行操作的整个文档。有关:

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

因此,手册中建议

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

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

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

这个答案与我在SO上的所有其他答案一起吸引了许多不赞成投票的人。人们似乎不喜欢这个想法:归一化设计对于非动态数据更胜一筹。克雷格·林格(Craig Ringer)撰写的精彩博客文章更详细地解释了:


6
此答案仅涉及JSON类型,而忽略JSONB。
fiatjaf 2015年

7
@fiatjaf:这个答案是完全适用于数据类型jsonjsonb一致好评。两者都存储JSON数据,jsonb并以标准化的二进制格式进行处理,该格式具有一些优点(而缺点很少)。stackoverflow.com/a/10560761/939860两种数据类型都不适合在数据库内部进行大量操作没有文件类型。好吧,这对于结构不大的小型JSON文档很好。但是那样的话,嵌套大文件是愚蠢的。
Erwin Brandstetter

7
实际上,“如何使用Postgres 9.3工具的说明”首先是您的答案,因为它回答了所问的问题。有时更新json以进行维护/架构更改等是有意义的,并且不进行json更新的原因不适用
并非

22
抱歉,此答案没有帮助。@jvous,您是否要接受Jimothy的回答,因为它确实回答了您的问题?
巴斯蒂安·沃伊特

10
在添加您自己的评论/意见/讨论之前,先回答问题。
Ppp

330

如果您能够升级到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索引):

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列中的值将替换为最终版本。


42
您会因按OP要求显示如何更新表中的列而获得奖励积分
chadrik '16

1
@chadrik:我添加了一个更复杂的示例。它不能完全满足您的要求,但可以给您一个想法。请注意,外部jsonb_set调用的输入是内部调用的输出,而内部调用的输入是的结果data #- '{tags,-1}'。即,删除了最后一个标签的原始数据。
Jimothy '16

1
@PranaySoni:为此,我可能会使用存储过程,或者,如果不担心开销,请带回该数据,以应用程序的语言对其进行操作,然后将其写回。这听起来很沉重,但是请记住,在我给出的所有示例中,您仍然没有不更新JSON(B)中的单个字段:您将以任何一种方式覆盖整个列。因此,存储的proc确实没有什么不同。
Jimothy

1
@Alex:是的,有点hack。如果我说的话{tags,0},那将意味着“数组的第一个元素tags”,允许我为该元素赋予一个新值。通过使用大数字而不是0,而不是代替数组中的现有元素,而是向数组中添加了一个新元素。但是,如果数组中实际上有超过999,999,999个元素,则它将替换最后一个元素,而不是添加一个新元素。
Jimothy

1
如果字段包含空值怎么办?看起来没用。例如,信息jsonb字段为空:“更新管理器SET信息= jsonb_set(info,'{country}','“ FRA”')其中info->>'country':: text IS NULL;“我得到UPDATE 105记录,但db上没有任何更改
stackdave


18

对于那些遇到此问题并想要快速修复(并且停留在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,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

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

有两个重要警告

  1. 这将替换json中所有称为“名称”的属性(如果您有多个具有相同名称的属性)
  2. 如果您使用的是9.5,则效率不如jsonb_set

如此说来,我遇到了这样一种情况,我必须更新jsonb对象中内容的架构,这是准确完成原始发布者要求的最简单的方法。


1
上帝,我一直在寻找如何对jsonb进行更新大约两个小时,以便可以替换所有\u0000空字符,示例显示了完整图片。谢谢你!
约书亚·罗宾逊

3
看起来挺好的!顺便说一句,在您的示例中要替换的第二个参数包括冒号,而第三个则不包括冒号。看来您的电话应该是replace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus

谢谢@davidicus!抱歉,更新时间太晚了,非常感谢您与他人分享!
乍得卡普拉

12

在postgres 9.4的上下文中提出了这个问题,但是新的查看者应该注意的是,在postgres 9.5中,数据库本机支持对JSONB字段的子文档Create / Update / Delete操作,而无需扩展功能。

请参阅:JSONB修改运算符和函数


7

更新“名称”属性:

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

以及是否要删除例如“名称”和“标签”属性:

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

5

我为自己编写了一个小的函数,该函数在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,

@Greg是的,我刚刚检查了一下,现在正在运行PostgreSQL 9.5-这就是为什么它可以工作。感谢您指出-我的解决方案在9.4版中不起作用。
J. Raczkiewicz


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.