Django erhält ein QuerySet aus einem Array von IDs in einer bestimmten Reihenfolge

84

Hier ist eine schnelle für Sie:

Ich habe eine Liste von IDs, mit denen ich ein QuerySet (oder ggf. ein Array) zurückgeben möchte, aber ich möchte diese Reihenfolge beibehalten.

Vielen Dank

neolaser
quelle

Antworten:

73

Ich glaube nicht, dass Sie diese bestimmte Reihenfolge auf Datenbankebene erzwingen können, also müssen Sie dies stattdessen in Python tun.

id_list = [1, 5, 7]
objects = Foo.objects.filter(id__in=id_list)

objects = dict([(obj.id, obj) for obj in objects])
sorted_objects = [objects[id] for id in id_list]

Dadurch wird ein Wörterbuch der Objekte mit ihrer ID als Schlüssel erstellt, sodass sie beim Erstellen der sortierten Liste leicht abgerufen werden können.

Reiner Gerecke
quelle
Ich hatte gehofft, dass dies nicht der Fall war :( Danke für den sauberen Code!
Neolaser
3
Achten Sie nur auf große Abfragen, da Sie das Ergebnis dabei im Speicher speichern.
Jj.
13
Verwenden Sie möglicherweise die Methode quersysets in_bulk (), anstatt das Wörterbuch selbst zu erstellen?
Matt Austin
Das Problem dabei ist, dass wenn ein Objekt in der id_list nicht existiert, es einen Fehler auslöst.
Madprops
Warum nicht das Diktatverständnis verwenden?
wieczorek1990
184

Seit Django 1.8 können Sie Folgendes tun:

from django.db.models import Case, When

pk_list = [10, 2, 1]
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(pk_list)])
queryset = MyModel.objects.filter(pk__in=pk_list).order_by(preserved)
Soitje
quelle
15
Beste Antwort, weil es tatsächlich ein Abfrageset zurückgibt
Teebes
2
Ich denke immer noch, dass Djangos Case Whenunterschätzt werden!
Babu
Ich werde distinct()mit order_by case when-Klausel verwenden, habe aber den Fehler erhalten. jede Unterstützung bitte.
Elquimista
SELECT DISTINCT ON expressions must match initial ORDER BY expressions- Hier ist die Fehlermeldung
Elquimista
1
Dies sollte die ausgewählte Antwort gewesen sein.
Subangkar KrS
28

Wenn Sie dies mit in_bulk tun möchten, müssen Sie die beiden obigen Antworten zusammenführen:

id_list = [1, 5, 7]
objects = Foo.objects.in_bulk(id_list)
sorted_objects = [objects[id] for id in id_list]

Andernfalls ist das Ergebnis eher ein Wörterbuch als eine speziell geordnete Liste.

Rick Westera
quelle
1
Dies ist keine bessere Antwort.
Tony
26

Hier ist eine Möglichkeit, dies auf Datenbankebene zu tun. Kopieren Einfügen von: blog.mathieu-leplatre.info :

MySQL :

SELECT *
FROM theme
ORDER BY FIELD(`id`, 10, 2, 1);

Gleiches gilt für Django:

pk_list = [10, 2, 1]
ordering = 'FIELD(`id`, %s)' % ','.join(str(id) for id in pk_list)
queryset = Theme.objects.filter(pk__in=[pk_list]).extra(
           select={'ordering': ordering}, order_by=('ordering',))

PostgreSQL :

SELECT *
FROM theme
ORDER BY
  CASE
    WHEN id=10 THEN 0
    WHEN id=2 THEN 1
    WHEN id=1 THEN 2
  END;

Gleiches gilt für Django:

pk_list = [10, 2, 1]
clauses = ' '.join(['WHEN id=%s THEN %s' % (pk, i) for i, pk in enumerate(pk_list)])
ordering = 'CASE %s END' % clauses
queryset = Theme.objects.filter(pk__in=pk_list).extra(
           select={'ordering': ordering}, order_by=('ordering',))
Nutzer
quelle
Beeindruckend. Das ist intensiv. Vielen Dank!
Dan Gayle
Danke für diese Antwort.
Subangkar KrS
9
id_list = [1, 5, 7]
objects = Foo.objects.filter(id__in=id_list)
sorted(objects, key=lambda i: id_list.index(i.pk))
Andrew G.
quelle
1
Bitte fügen Sie einen Text hinzu, der erklärt, wie dies funktioniert und warum es das Problem des OP lösen würde. Helfen Sie anderen zu verstehen.
APC
Beste Antwort. Vielen Dank!
Webjunkie
Funktioniert gut, könnte aber zusätzliche Informationen verwenden
xtrinch
Dies funktioniert nur für die bestellte ID-Liste. Können Sie bestätigen, ob es in irgendeiner Reihenfolge funktioniert ... scheint mir nicht wahr zu sein.
Doogle