Django pobiera QuerySet z tablicy identyfikatorów w określonej kolejności

84

oto szybki dla ciebie:

Mam listę identyfikatorów, których chcę użyć, aby zwrócić QuerySet (lub tablicę, jeśli trzeba), ale chcę zachować tę kolejność.

Dzięki

neolaser
źródło

Odpowiedzi:

72

Nie sądzę, abyś mógł wymusić tę konkretną kolejność na poziomie bazy danych, więc zamiast tego musisz to zrobić w Pythonie.

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]

Tworzy to słownik obiektów z ich identyfikatorem jako kluczem, dzięki czemu można je łatwo odzyskać podczas tworzenia posortowanej listy.

Reiner Gerecke
źródło
Miałem nadzieję, że tak nie jest :( dzięki za czysty kod!
neolaser,
3
Po prostu uważaj na duże zapytania, ponieważ podczas wykonywania tej czynności będziesz przechowywać wynik w pamięci.
Jj.
14
Może użyj metody querysets in_bulk () zamiast samodzielnego tworzenia słownika?
Matt Austin,
Problem z tym polega na tym, że jeśli obiekt w id_list nie istnieje, zgłosi błąd.
madprops
Dlaczego nie użyć dyktowania ze zrozumieniem?
wieczorek1990
186

Od Django 1.8 możesz:

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
źródło
16
Najlepsza odpowiedź, ponieważ w rzeczywistości zwraca zestaw zapytań
Teebes
2
Nadal uważam, że django Case Whensą niedoceniane!
Babu
Zamierzam użyć distinct()z klauzulą ​​order_by, gdy wystąpił błąd. jakieś wsparcie, proszę.
elquimista
SELECT DISTINCT ON expressions must match initial ORDER BY expressions- tutaj jest komunikat o błędzie
elquimista
1
To powinna być wybrana odpowiedź.
Subangkar KrS
28

Jeśli chcesz to zrobić za pomocą in_bulk, w rzeczywistości musisz połączyć dwie powyższe odpowiedzi:

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

W przeciwnym razie wynikiem będzie słownik, a nie specjalnie uporządkowana lista.

Rick Westera
źródło
1
To nie jest lepsza odpowiedź.
Tony
26

Oto sposób na zrobienie tego na poziomie bazy danych. Skopiuj wklej z: blog.mathieu-leplatre.info :

MySQL :

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

To samo z 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;

To samo z 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',))
użytkownik
źródło
Łał. To jest intensywne. Dzięki!
Dan Gayle
Dzięki za tę odpowiedź.
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.
źródło
1
Dodaj tekst wyjaśniający, jak to działa i dlaczego rozwiązałoby to problem PO. Pomóż innym zrozumieć.
APC
Najlepsza odpowiedź. Dzięki!
webjunkie
Działa dobrze, ale
przydałoby się
będzie to działać tylko dla zamówionej listy identyfikatorów. Czy możesz potwierdzić, czy zadziała w dowolnej kolejności ... wydaje mi się to nieprawdą.
Doogle