Как связать запросы 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")'.
Правильный ответ во многом зависит от того, почему вы хотите объединить их и как вы собираетесь использовать результаты.
Ответ 2
Для Django 1.11 (выпущено 4 апреля 2017 г.) используйте для этого union(), документация здесь:
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union
Вот ссылка Версии 2.1 на это: https://docs.djangoproject.com/en/2.1/ref/models/querysets/#union
Ответ 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')
При этом я мог бы упорядочить полученное объединение так, как я хотел, без изменения какого-либо частного атрибута, при этом оценивая наборы запросов только один раз.