浏览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;

这是否被记录在我遗漏的某个地方?谢谢。


当前回答

这个问题是在postgres 9.4中提出的, 然而,新来的观众应该知道,在postgres 9.5中, 数据库本身支持对JSONB字段的创建/更新/删除操作,不需要扩展功能。

参见:JSONB修改操作符和函数

其他回答

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

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

带索引的JSONB vs. hstore

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

因此手册中的建议是:

JSON data is subject to the same concurrency-control considerations as any other data type when stored in a table. Although storing large documents is practicable, keep in mind that any update acquires a row-level lock on the whole row. Consider limiting JSON documents to a manageable size in order to decrease lock contention among updating transactions. Ideally, JSON documents should each represent an atomic datum that business rules dictate cannot reasonably be further subdivided into smaller datums that could be modified independently.

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

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

如何修改新的PostgreSQL JSON数据类型中的字段?

这个答案吸引的反对票和我在SO上的所有其他答案加起来一样多。人们似乎不喜欢这个想法:规范化设计对于规则数据更优越。Craig Ringer的这篇精彩的博客文章详细解释了这一点:

PostgreSQL反模式:不必要的json/hstore动态列

Laurenz Albe(与Craig和我一样是Postgres的官方贡献者)的另一篇博文:

PostgreSQL中的JSON:如何正确使用它

对于那些遇到这个问题并想要快速修复的人(并且被困在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那么高效了

如果你能够升级到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-index):

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数据的单个字段。相反,您将创建数据的临时修改版本,并将修改后的版本分配回列。在实践中,结果应该是相同的,但是记住这一点可以使复杂的更新(如上一个例子)更容易理解。

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

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

正如你所看到的,它深入分析并在需要的地方更新/添加值。

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

9.3 json https://gist.github.com/matheusoliveira/9488951

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