【问题标题】:Django / PostgresQL jsonb (JSONField) - convert select and update into one queryDjango / PostgresQL jsonb (JSONField) - 将选择和更新转换为一个查询
【发布时间】:2017-06-20 18:58:40
【问题描述】:

版本:Django 1.10 和 Postgres 9.6

我正在尝试修改嵌套 JSONField 的键,而无需往返 Python。原因是为了避免竞争条件和多个查询用不同的更新覆盖相同的字段。

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

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

from exampleapp.models import AdhocTask

record = AdhocTask.objects.get(id=1)
print(record.log)
> {'demo_key': 'original'}

查询:

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’))
)

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

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'}]

【问题讨论】:

    标签: json django postgresql jsonb


    【解决方案1】:

    没有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,
        )
    

    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

    【讨论】:

    • 这段代码很漂亮,谢谢迈克尔。我以前没有继承Func 的经验,所以这是一个很好的指针。也很高兴在实践中看到类型提示;它绝对有助于提高可读性和理解代码的意图。
    • 你救了我的命
    【解决方案2】:

    最好的橡皮鸭调试 - 在编写问题时,我已经实现了解决方案。在这里留下答案,希望将来可以帮助某人:

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

    解决方案

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

    (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'}
    

    【讨论】:

      猜你喜欢
      • 2014-01-05
      • 2019-01-18
      • 2018-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-08
      • 1970-01-01
      • 2018-09-09
      相关资源
      最近更新 更多