Как связать запросы Django с сохранением индивидуального заказа

Я хотел бы добавить или связать несколько Querysets в Django, сохраняя порядок каждого из них (а не результат). Я использую стороннюю библиотеку для разбивки на результат, и он принимает только списки или запросы. Я пробовал эти параметры:

Queryset join: не сохраняет порядок в отдельных запросах, поэтому я не могу использовать это.

result = queryset_1 | queryset_2

Использование itertools. Вызов list() в цепочном объекте фактически оценивает запросы, и это может вызвать много накладных расходов. Не так ли?

result = list(itertools.chain(queryset_1, queryset_2))

Как вы думаете, я должен идти?

Ответ 1

Если наборы запросов имеют разные модели, вы должны оценить их в списки, а затем вы можете просто добавить:

result = list(queryset_1) + list(queryset_2)

Если это одна и та же модель, вы должны объединить запросы, используя объект Q и 'order_by ("поле queryset_1", "поле queryset_2")'.

Правильный ответ во многом зависит от того, почему вы хотите объединить их и как вы собираетесь использовать результаты.

Ответ 3

Я не уверен на 100%, что это решение работает во всех возможных случаях, но похоже, что в результате получается объединение двух QuerySets (в одной модели), сохраняющих порядок первого:

union = qset1.union(qset2)
union.query.extra_order_by = qset1.query.extra_order_by
union.query.order_by = qset1.query.order_by
union.query.default_ordering = qset1.query.default_ordering
union.query.get_meta().ordering = qset1.query.get_meta().ordering

Я не тестировал его всесторонне, поэтому, прежде чем использовать этот код в рабочей среде, убедитесь, что он работает так, как ожидалось.

Ответ 4

Если вам нужно объединить два запроса в третий запрос, вот пример, используя _result_cache.

модель

class ImportMinAttend(models.Model):
    country=models.CharField(max_length=2, blank=False, null=False)
    status=models.CharField(max_length=5, blank=True, null=True, default=None)

Из этой модели я хочу отобразить список всех строк таким образом, что:

  • (запрос 1) пустой статус сначала, упорядоченный по странам
  • (запрос 2) непустой статус идет вторым, упорядоченный по странам

Я хочу объединить запрос 1 и запрос 2.

    #get all the objects
    queryset=ImportMinAttend.objects.all()

    #get the first queryset
    queryset_1=queryset.filter(status=None).order_by("country")
    #len or anything that hits the database
    len(queryset_1)

    #get the second queryset
    queryset_2=queryset.exclude(status=None).order_by("country")

    #append the second queryset to the first one AND PRESERVE ORDER
    for query in queryset_2:
         queryset_1._result_cache.append(query)

    #final result
    queryset=queryset_1

Это может быть не очень эффективно, но работает:).

Ответ 5

Если два набора запросов имеют общее поле, вы можете упорядочить комбинированный набор запросов по этому полю. Наборы запросов не оцениваются во время этой операции.

Например:

class EventsHistory(models.Model):
    id = models.IntegerField(primary_key=True)
    event_time = models.DateTimeField()
    event_id = models.IntegerField()

class EventsOperational(models.Model):
    id = models.IntegerField(primary_key=True)
    event_time = models.DateTimeField()
    event_id = models.IntegerField()

qs1 = EventsHistory.objects.all()
qs2 = EventsOperational.objects.all()

qs_combined = qs2.union(qs1).order_by('event_time')

Ответ 6

Итак, вдохновленный Питером ответ, это то, что я сделал в своем проекте (кстати, Django 2.2):

from django.db import models
from .models import MyModel

# Add an extra field to each query with a constant value
queryset_0 = MyModel.objects.annotate(
    qs_order=models.Value(0, models.IntegerField())

# Each constant should basically act as the position where we want the 
# queryset to stay
queryset_1 = MyModel.objects.annotate(
    qs_order=models.Value(1, models.IntegerField()) 

[...]

queryset_n = MyModel.objects.annotate(
    qs_order=models.Value(n, models.IntegerField()) 

# Finally, I ordered the union result by that extra field.
union = queryset_0.union(
    queryset_1, 
    queryset_2, 
    [...], 
    queryset_n).order_by('qs_order')

При этом я мог бы упорядочить полученное объединение так, как я хотел, без изменения какого-либо частного атрибута, при этом оценивая наборы запросов только один раз.