2017-02-03 10 views
2

バージョンに更新:ジャンゴ1.10とPostgresの9.6ジャンゴ/ PostgreSQLのjsonb(JSONField) - 選択変換し、1つのクエリ

私は、Pythonへのラウンドトリップなし代わりに、ネストされたJSONFieldのキーを変更しようとしています。理由は競合状態と複数の照会が同じフィールドを別の更新で上書きするのを避けることです。

オリジナルのフィールド値(デモのみ、実際のデータは、より複雑です):

私はDjangoは単一のクエリになるだろうが、それは2として記録されていますことをチェーンに希望のメソッドを試してみました

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

ケRY履歴(二つの別々のクエリなどを示して、この):

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

答えて

1
その最高の状態で

ラバーダックのデバッグ - 質問を書面で、私は解決策を実現しました。クエリを見ると、実際にはRawSQLがクエリ2まで延期されていることに気付きました。つまり、後で実行するためにサブクエリとして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'} 
2

それは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は別の方法で参照されます。コードの美しい作品ですhttps://www.postgresql.org/docs/9.6/static/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE

+0

、感謝マイケル:

jsonb_setに関する詳細に興味を持って誰でもpostgresのドキュメント内の表9-45を見ている必要があります。私は 'Func'を継承した経験がありませんでした。実際にタイプヒントを見るのも良いです。コードの可読性と意図を理解することは間違いありません。 –

関連する問題