2017-02-03 3 views
2

Versionen: Django 1.10 und Postgres 9.6Django/PostgresQL jsonb (JSONField) - konvertieren auswählen und aktualisieren in einer Abfrage

Ich versuche, ohne eine Rundreise zu Python eine verschachtelte JSONField der Schlüssel in Position zu ändern. Grund ist es, Race Conditions und Mehrfachabfragen zu vermeiden, die das gleiche Feld mit unterschiedlichen Updates überschreiben.

Ich versuchte Kette, die Methoden in der Hoffnung, dass Django eine einzelne Abfrage machen würde, aber es als Zweierkomplement wird protokolliert:

Original-Feldwert (Demo nur reale Daten sind komplexer):

from exampleapp.models import AdhocTask 

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

Abfrage:

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

Que Geschichte ry (zeigt dies als zwei getrennte Abfragen):

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

Antwort

1

Gummiente Debuggen von seiner besten Seite - in der Frage zu schreiben, ich habe die Lösung realisiert. Verlassen Sie die Antwort hier in der Hoffnung, jemand in Zukunft helfen:

bei den Abfragen Blick erkennen ich, dass die RawSQL wurde tatsächlich bis zwei Abfrage verschoben werden, so dass alles, was ich tat, war die RawSQL als Unterabfrage für die spätere Ausführung gespeichert werden.

Lösung:

überspringen annotate Schritt zusammen und verwenden Sie den RawSQL Ausdruck direkt in den .update() Anruf. Damit können Sie dynamisch aktualisieren PostgresQL jsonb Unterschlüssel auf dem Datenbank-Server, ohne das gesamte Feld überschreiben:

(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

Es wäre viel schöner ohne RawSQL.

Hier ist, wie es zu tun:

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 die gleiche wie Ihre RAW-SQL-Anweisung, nur parametrisiert.

(jsonb_set(log, \'{"demo_key"}\',\'"another value"\', false)) von Ihrer Abfrage ist jetzt jsonb_set("exampleapp.adhoctask"."log", \'{"demo_key"}\',\'"another value"\', false). Die Klammern sind weg (Sie können sie erhalten, indem Sie sie zur Vorlage hinzufügen) und log wird auf eine andere Weise referenziert.

Wer in mehr Details interessiert jsonb_set bezüglich sollten einen Blick auf Tisch 9-45 in Postgres' Dokumentation: https://www.postgresql.org/docs/9.6/static/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE

+0

Das ist ein schönes Stück Code ist dank Michael. Ich hatte noch keine Erfahrung, 'Func' vorher zu erben, also ist das ein großartiger Zeiger. Auch gut zu sehen Typ Hinting in der Praxis; es hilft definitiv Lesbarkeit und die Absicht für den Code zu verstehen. –

Verwandte Themen