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

jvo*_*ous 110 postgresql crud sql-update jsonb postgresql-9.4

查看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);
Run Code Online (Sandbox Code Playgroud)

插入很简单,如:

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');
Run Code Online (Sandbox Code Playgroud)

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

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

这个文件记录在哪里,我错过了吗?谢谢.

Jim*_*thy 253

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

在以下每个SQL语句中,where为了简洁,我省略了该子句; 显然,你想要添加它.

更新名称:

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');
Run Code Online (Sandbox Code Playgroud)

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

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');
Run Code Online (Sandbox Code Playgroud)

替换第二个标记(0索引):

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');
Run Code Online (Sandbox Code Playgroud)

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

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);
Run Code Online (Sandbox Code Playgroud)

删除最后一个标记:

UPDATE test SET data = data #- '{tags,-1}'
Run Code Online (Sandbox Code Playgroud)

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

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');
Run Code Online (Sandbox Code Playgroud)

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

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

  • 如果OP要求,您将获得奖励积分以显示如何更新表格中的列 (35认同)
  • 如果字段包含空怎么办?看起来不起作用。例如 info jsonb 字段为空: "UPDATE Organizer SET info = jsonb_set(info, '{country}', '"FRA"') where info->>'country'::text IS NULL; " 我得到 UPDATE 105 记录,但是数据库没有变化 (2认同)

Erw*_*ter 35

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

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

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

因此手册中建议:

当存储在表中时,JSON数据与任何其他数据类型具有相同的并发控制注意事项.虽然存储大型文档是切实可行的,但请记住,任何更新都会在整行上获取行级锁定.考虑将JSON文档限制为可管理的大小,以减少更新事务之间的锁争用.理想情况下,JSON文档应该各自代表业务规则所规定的原子数据,不能合理地进一步细分为可以独立修改的较小基准.

它的要点:要修改JSON对象内的任何内容,必须将修改后的对象分配给该列.json除了存储功能之外,Postgres还提供有限的构建和操作数据的方法.自9.2版以来,每一个新版本的工具都在大幅增长.但是主体仍然存在:您总是必须为列分配完整的修改对象,Postgres总是为任何更新编写新的行版本.

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

这个答案已经吸引了许多downvotes上所以我所有其他的答案在一起.人们似乎不喜欢这个想法:标准化设计优于非动态数据.这篇由Craig Ringer撰写的优秀博客文章更详细地解释道:

  • 抱歉,这个答案没有帮助.@jvous,你不想接受Jimothy的回答,因为它真的回答了你的问题吗? (9认同)
  • @fiatjaf:这个答案完全适用于数据类型`json`和`jsonb`.两者都存储JSON数据,`jsonb`以标准化的二进制形式执行,具有一些优点(并且缺点很少).http://stackoverflow.com/a/10560761/939860这两种数据类型都不适合在数据库中进行*操作*.***没有***文件类型.嗯,对于小的,难度很小的JSON文档来说很好.但是大的,嵌套的文档将是一种愚蠢的方式. (7认同)
  • 此答案仅涉及JSON类型并忽略JSONB. (5认同)
  • "有关如何使用Postgres 9.3工具的说明"真的应该是你的答案中的第一个,因为它回答了所提出的问题..有时候更新json以进行维护/架构更改等以及不做更新的原因json don真的适用 (5认同)
  • 在添加您自己的评论/意见/讨论之前,先回答问题。 (4认同)
  • 不确定它是否与否决票有关(我没有投反对票),但问题可能更多,因为它是“你不应该这样做”的答案,而不是“人们似乎不喜欢这个想法:标准化的……”问题。我认为您是对的,在 JSON/B 中存储大量内容可能表明存在更大的问题并且远非理想,但是我们中的很多人必须使用预先存在的系统,并且需要学习如何使用我们拥有的系统直到我们能够改进这些系统。 (2认同)
  • @taleodor:JSON 支持在每个版本中都得到了改进,并且目前已经非常出色。已经有一段时间了。对于某些应用程序非常有用。但我的答案仍然***完全适用*** - 特别是对于这个问题所询问的“更新操作” - 因为它解决了文档类型的原则限制。对于常规数据,或多或少规范化的数据库模式中的适当列通常“更加”高效。这不会改变。Postgres 项目提出了相应的建议,就像我上面引用的那样 - 直到 Postgres 13 开发手册为止都没有改变。 (2认同)
  • 除了@MichaelWasser 评论之外 - 标准化的概念与OP的问题无关。正常化完全超出了范围,并且有其自身的优点、缺点和理由。 (2认同)
  • 人们首先应该知道为什么要这样做。热爱哲学。 (2认同)
  • 阅读此答案时要非常小心。对于这个答案中列出的每一篇博文,对此都有平等和相反的观点。 (2认同)

phi*_*est 23

这是由Andrew Dunstanjsonb_set的形式在9.5中基于现有的扩展jsonbx,它与9.4一起工作


Cha*_*pra 17

对于那些遇到这个问题并希望快速修复(并且仍然停留在9.4.5或更早版本)的人来说,这就是我所做的:

创建测试表

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');
Run Code Online (Sandbox Code Playgroud)

更新语句以更改jsonb属性的名称

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

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

有两个重要的警告

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

话虽如此,我遇到了一种情况,我必须更新jsonb对象中内容的模式,这是完成原始海报所要求的最简单的方法.

  • 看起来不错!顺便说一句,在您的示例中要替换的第二个参数包括冒号,而第三个则不包括冒号。看来您的通话应该是`replace(data :: TEXT,'“ name”:','“ my-other-name”:'):: jsonb` (3认同)
  • 天哪,我一直在寻找如何更新 jsonb 大约两个小时,以便我可以替换所有 `\u0000` 空字符,示例显示了完整的图片。谢谢你! (2认同)

Art*_*hur 12

更新“名称”属性:

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

如果您想删除例如 'name' 和 'tags' 属性:

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)


bgu*_*uiz 10

这个问题是在postgres 9.4的背景下提出的,但是对于这个问题的新观众应该知道,在postgres 9.5中,数据库本身支持JSONB字段上的子文档创建/更新/删除操作,而不需要扩展功能.

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


小智 5

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

它适用于我的案例,其中数据是 json 类型


J. *_*icz 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;
Run Code Online (Sandbox Code Playgroud)

这是示例用法:

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)
Run Code Online (Sandbox Code Playgroud)

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