2016-07-26 12 views
0

Ich habe meinen Kopf gegen die Wand geschlagen, um diese Abfrage mit dem Django ORM zu machen, ich suchte überall, konnte aber keine Antwort finden. Das ist mein Modell.Abhängige Unterabfragen in Django

class Decision(models.Model): 
    ACTION_CHOICES = [('include', _('Include')), ('exclude', _('Exclude'))] 
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 
    date_taken = models.DateTimeField(_('date taken'), default=timezone.now) 
    action = models.CharField(max_length=7, choices=ACTION_CHOICES) 
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 
    object_id = models.PositiveIntegerField() 
    content_object = GenericForeignKey('content_type', 'object_id') 
    project = models.ForeignKey('projects.Project', on_delete=models.CASCADE) 
    is_permanent = models.BooleanField(_('is permanent'), default=False) 

Und was ich will abzufragen sind die neuesten permanenten Entscheidungen für jeden content_object, dass ein Benutzer in einem Projekt gemacht hat, da diese Abfrage wiederholt in der Anwendung aufgerufen wird, wie Decision.filter(user_id=user.pk, project_id=project.pk) durch ein einfacheres QuerySet Iterieren die bekommen Endergebnis ist keine Option. Bisher habe ich es mit einem RawQuerySet wie diesem gelöst.

Decision.objects.raw("SELECT decision.id, decision.content_type_id, decision.object_id " 
        "FROM canvasblocks_decision AS decision " 
        "WHERE decision.date_taken = (SELECT max(last_decision.date_taken) " 
        "        FROM canvasblocks_decision AS last_decision " 
        "        WHERE last_decision.object_id = decision.object_id AND " 
        "         last_decision.content_type_id = decision.content_type_id AND " 
        "         last_decision.project_id = %s AND decision.user_id = %s) ", 
        [project.pk, user.pk]) 

Aber ich glaube nicht, diese Lösung gefällt, weil ich alle Django ORM Macht verlieren und da ich diese Abfrage erneut zu filtern, Maßnahmen zu prüfen, dass die Macht verlieren, es ist schmerzhaft, weil ich dies auf einem zu transformieren Unterabfrage mit all der Komplexität, die damit einhergeht. Also, Jungs, weißt du einen besseren Weg, dies zu tun? Übrigens verwende ich Django 1.9.4. Danke im Voraus.

Antwort

0

Wenn das Dataset klein ist, können Sie mehrere Pässe erstellen, aber das ist keine sehr gute Idee.

Ein paar Optionen, abhängig von der Datenbank, die Sie verwenden.

1) Sie können die SQL ändern, um rank oder dense_rank Funktion zu verwenden, um die Abfrage viel einfacher zu machen.

2) Sie könnten dieselbe Logik in eine Annotation einfügen, um den Rang zu erhalten. Auf diese Weise haben Sie alles, was Ihr Django-Objekt gibt und Sie erhalten diese zusätzliche Spalte.

from django.db.models.expressions import RawSQL 
Decision.objects.filter().annotate(rank=RawSQL("RANK() OVER (partition by id, content_type, object_id 
            (ORDER BY date_taken DESC)", []) 
           ) 

..

Dies könnte helfen: https://stackoverflow.com/a/35948419/237939

+0

Dank @Rajesh Chamarthi. Ich muss meine DB zu Postgres migrieren! So viele erstaunliche Funktionen! Ich verstehe nicht vollständig die erste Option, die Sie mir gegeben haben, die [Postrgres Fensterfunktionen] (https://www.postgresql.org/docs/devel/static/functions-window.html) Ihrer Antwort sind völlig neu für Ich nehme an, dass 'first_value (...)' die Zeile mit dem höchsten Rang in der Partition nimmt, aber ich sehe nicht, von wo 'last_decision.date_taken' kommt, in der zweiten Option werde ich a machen '.filter (rank = 1)' um das Endergebnis zu erhalten? –

+0

Ja, Sie müssen Rang = 1 filtern. Ich habe auch die Antwort bearbeitet, um die Partitionsklausel hinzuzufügen. Entschuldigung, es ist im Moment nicht getestet, da ich keinen Zugriff auf eine Instanz mit Django habe. Postgres ist übrigens eine großartige Option! –

+0

Mach dir keine Sorgen, ich werde das später testen, ich denke endlich verstanden, was meinst du mit der ersten Option, [dieses Video] (https://www.youtube.com/watch?v=KwEjkpFltjc) hilft, ' OVER (PARTITION BY ...) verhält sich so, als wäre meine Unterabfrage eine GROUP BY, aber viel einfacher. In diesem Fall haben Sie jede Zeile decision.date_taken des neueren Elements hinzugefügt und diese beiden Daten in der Datei verglichen WO, um die Spiele auszuwählen, richtig? Korrigiere mich, wenn ich falsch liege, ich denke, es fehlt nur ein Alias ​​für die 'OVER (PARTITION BY ...)' und wäre 'decision.date_taken' anstelle von' last_decision.date_taken'? Danke noch einmal. –