Django / PostgresQL jsonb (JSONField) - 转换 select 并更新为一个查询
Django / PostgresQL jsonb (JSONField) - convert select and update into one query
版本: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'}]
Rubber duck debugging at its best - 在写问题时,我已经意识到了解决方案。在这里留下答案,希望对以后的人有所帮助:
查看查询,我意识到 RawSQL 实际上被推迟到查询二,所以我所做的只是将 RawSQL
存储为子查询以供以后执行。
解决方案:
完全跳过 annotate
步骤,直接在 .update()
调用中使用 RawSQL
表达式。允许您在数据库服务器上动态更新 PostgresQL jsonb sub-keys 而无需覆盖整个字段:
(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'}
没有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 文档中的 table 9-45:https://www.postgresql.org/docs/9.6/static/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE
版本: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'}]
Rubber duck debugging at its best - 在写问题时,我已经意识到了解决方案。在这里留下答案,希望对以后的人有所帮助:
查看查询,我意识到 RawSQL 实际上被推迟到查询二,所以我所做的只是将 RawSQL
存储为子查询以供以后执行。
解决方案:
完全跳过 annotate
步骤,直接在 .update()
调用中使用 RawSQL
表达式。允许您在数据库服务器上动态更新 PostgresQL jsonb sub-keys 而无需覆盖整个字段:
(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'}
没有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 文档中的 table 9-45:https://www.postgresql.org/docs/9.6/static/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE