如何在 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:此答案完全适用于json
和 jsonb
等数据类型。两者都存储 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 9.4 将 JSON 元素附加到数组
Postgresql 9.4 - 转换为 JSONB 时输入语法无效