Django/PostgresQL jsonb(JSONField) - 将select和update转换为一个查询

Phi*_*ard 3 django postgresql json jsonb

版本:Django 1.10和Postgres 9.6

我试图在没有往返Python的情况下修改嵌套的JSONField密钥.原因是避免竞争条件和多个查询用不同的更新覆盖相同的字段.

我试图链接这些方法,希望Django可以进行单个查询,但它被记录为两个:

原始字段值(仅演示,实际数据更复杂):

from exampleapp.models import AdhocTask

record = AdhocTask.objects.get(id=1)
print(record.log)
> {'demo_key': 'original'}
Run Code Online (Sandbox Code Playgroud)

查询:

from django.db.models import F
from django.db.models.expressions import RawSQL

(AdhocTask.objects.filter(id=25)
                  .annotate(temp=RawSQL(
                      # `jsonb_set` gets current json value of `log` field,
                      # take a the nominated key ("demo key" in this example)
                      # and replaces the value with the json provided ("new value")
                      # Raw sql is wrapped in triple quotes to avoid escaping each quote                           
                      """jsonb_set(log, '{"demo_key"}','"new value"', false)""",[]))
                  # Finally, get the temp field and overwrite the original JSONField
                  .update(log=F('temp’))
)
Run Code Online (Sandbox Code Playgroud)

查询历史记录(将其显示为两个单独的查询):

from django.db import connection
print(connection.queries)

> {'sql': 'SELECT "exampleapp_adhoctask"."id", "exampleapp_adhoctask"."description", "exampleapp_adhoctask"."log" FROM "exampleapp_adhoctask" WHERE "exampleapp_adhoctask"."id" = 1', 'time': '0.001'},
> {'sql': 'UPDATE "exampleapp_adhoctask" SET "log" = (jsonb_set(log, \'{"demo_key"}\',\'"new value"\', false)) WHERE "exampleapp_adhoctask"."id" = 1', 'time': '0.001'}]
Run Code Online (Sandbox Code Playgroud)

Mic*_*ael 5

没有它会好得多RawSQL.

这是怎么做的:

from django.db.models.expressions import Func


class ReplaceValue(Func):

    function = 'jsonb_set'
    template = "%(function)s(%(expressions)s, '{\"%(keyname)s\"}','\"%(new_value)s\"', %(create_missing)s)"
    arity = 1

    def __init__(
        self, expression: str, keyname: str, new_value: str,
        create_missing: bool=False, **extra,
    ):
        super().__init__(
            expression,
            keyname=keyname,
            new_value=new_value,
            create_missing='true' if create_missing else 'false',
            **extra,
        )


AdhocTask.objects.filter(id=25) \
    .update(log=ReplaceValue(
        'log',
        keyname='demo_key',
        new_value='another value',
        create_missing=False,
    )
Run Code Online (Sandbox Code Playgroud)

ReplaceValue.template 与原始SQL语句相同,只是参数化.

(jsonb_set(log, \'{"demo_key"}\',\'"another value"\', false))从您的查询现在jsonb_set("exampleapp.adhoctask"."log", \'{"demo_key"}\',\'"another value"\', false).括号已经消失(您可以通过将其添加到模板中来取回它们)并log以不同的方式引用它们.

任何对更多细节感兴趣的人都jsonb_set应该查看postgres文档中的表9-45:https://www.postgresql.org/docs/9.6/static/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE


Phi*_*ard 4

橡皮鸭调试处于最佳状态 - 在写下问题时,我已经实现了解决方案。在这里留下答案希望对将来的人有所帮助:

查看查询,我意识到 RawSQL 实际上被推迟到查询二,所以我所做的就是将其存储RawSQL为子查询以供以后执行。

解决方案

完全跳过该步骤并直接在调用中annotate使用表达式。允许您动态更新数据库服务器上的 PostgresQL jsonb 子键,而无需覆盖整个字段:RawSQL.update()

(AdhocTask.objects.filter(id=25)
    .update(log=RawSQL(
                """jsonb_set(log, '{"demo_key"}','"another value"', false)""",[])
                )
)
> 1  # Success

print(connection.queries)
> {'sql': 'UPDATE "exampleapp_adhoctask" SET "log" = (jsonb_set(log, \'{"demo_key"}\',\'"another value"\', false)) WHERE "exampleapp_adhoctask"."id" = 1', 'time': '0.001'}]

print(AdhocTask.objects.get(id=1).log)
> {'demo_key': 'another value'}
Run Code Online (Sandbox Code Playgroud)