如何链接 Django 查询集以保留单个顺序

Posted

技术标签:

【中文标题】如何链接 Django 查询集以保留单个顺序【英文标题】:How to chain Django querysets preserving individual order 【发布时间】:2013-08-16 14:57:25 【问题描述】:

我想在 Django 中附加或链接几个查询集,保留每个查询集的顺序(而不是结果)。我正在使用第三方库对结果进行分页,它只接受列表或查询集。我尝试了以下选项:

Queryset join:不保留单个查询集中的顺序,所以我不能使用它。

result = queryset_1 | queryset_2

使用 itertools:在链对象上调用 list() 实际上会评估查询集,这可能会导致大量开销。不是吗?

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

你觉得我应该怎么走?

【问题讨论】:

【参考方案1】:

此解决方案可防止重复:

q1 = Q(...)
q2 = Q(...)
q3 = Q(...)
qs = (
    Model.objects
    .filter(q1 | q2 | q3)
    .annotate(
        search_type_ordering=Case(
            When(q1, then=Value(2)),
            When(q2, then=Value(1)),
            When(q3, then=Value(0)),
            default=Value(-1),
            output_field=IntegerField(),
        )
    )
    .order_by('-search_type_ordering', ...)
)

【讨论】:

这具有允许过滤结果查询集的额外好处。 IMO,这是迄今为止的最佳答案! 我喜欢这个答案的想法。 如果Q 对象过滤器生成LEFT JOIN,其中一些Model 对象在CASE 中进行了多次比较,这可能会导致重复。请参阅此以获得更好的解决方案:***.com/q/38583295/9835872【参考方案2】:

所以,受Peter's answer 的启发,这就是我在我的项目(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')

有了这个,我可以在不更改任何私有属性的情况下按我的意愿订购生成的联合,同时只评估一次查询集。

【讨论】:

这种方法给我带来了麻烦。应用联合后,无法从查询中过滤或获取 values_list。 是的……这就是联合的​​问题。如文档 (docs.djangoproject.com/en/2.2/ref/models/querysets/#union) 中所述,您无法过滤生成的查询集。你必须在联合之前过滤... union 的行为是删除重复项。但在应用此注释后,会出现 order_by 重复项。 @SandeepBalagopal,因为你必须使用 UNION ALL,但我不认为 Django ORM 有这个......如果我曾经有过用例,我想我将不得不使用一个原始的 sql 语句。【参考方案3】:

union() 函数将多个查询集组合在一起,而不是 or (|) 运算符。这避免了读取整个表的非常低效的 OUTER JOIN 查询。

【讨论】:

【参考方案4】:

对于 Django 1.11(2017 年 4 月 4 日发布)使用 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

【讨论】:

拒绝投票,因为这不会保留查询集的顺序。【参考方案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】:

我不能 100% 确定这个解决方案在所有可能的情况下都有效,但看起来结果是两个 QuerySet(在同一模型上)的并集,保留了第一个的顺序:

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

我没有对它进行广泛的测试,所以在您在生产中使用该代码之前,请确保它的行为符合预期。

【讨论】:

【参考方案7】:

如果您需要将两个查询集合并到第三个查询集,这里有一个示例,使用_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

它可能效率不高,但它确实有效:)。

【讨论】:

这似乎比在两个查询集上调用 list() 更有效。如果您遍历最小的,这似乎更好。我不喜欢的是修改私有属性_result_cache...你确定这样安全吗? 此方法仍将评估所有查询集,这与创建问题作者不想要的列表相同。【参考方案8】:

如果查询集是不同的模型,您必须将它们评估为列表,然后您可以追加:

result = list(queryset_1) + list(queryset_2)

如果它们是相同的模型,您应该使用 Q object 和 'order_by("queryset_1 field", "queryset_2 field")' 组合查询。

正确的答案很大程度上取决于您为什么要结合这些以及您将如何使用这些结果。

【讨论】:

我正在对拆分为多个查询的同一模型进行复杂的搜索。每个都检索与特定条件匹配的记录,并且每个记录都以特定方式排序。结果必须包括来自每个查询集的结果,并且必须维护每个查询集的顺序。因此,我不能在这里使用Q 对象,因为不允许我在同一个查询上执行多个order_by()。我想避免在每个查询集上调用list() 以避免访问数据库,在内存中获取太多对象。 您是否认为可以编写一个纯 SQL 查询,该查询将返回一组完全按照您的需要排序和过滤的行?如果没有,那么单个 QuerySet 也无法做到。例如,如果您以不兼容的方式订购两个结果集。如果您使用来自两个明显排序的结果集的复杂连接来解决这种不兼容性,那不是 django ORM 可以做的。 我不想尽可能多地使用纯 SQL,因为我可以继续使用 Django 的 ORM。我只是问这个问题,看看是否有更好的替代方法来代替我正在做的事情(目前使用第二个示例),并通过有限的查询来避免内存中有数千个对象。 我不是建议你做纯 SQL。 “是否可以在此处使用单个 SQL 查询而无需复杂的连接?”这个问题的答案。与“是否可以使用单个 QuerySet?”相同。如果您可以编写这样的查询,您可以推理如何编写类似的 QS。 是的,我可以在 SQL 中进行查询,但我会使用 SQL 联合(虽然我不知道它们是否保留顺序)

以上是关于如何链接 Django 查询集以保留单个顺序的主要内容,如果未能解决你的问题,请参考以下文章

如何保留 $in 查询的游标顺序? [复制]

如何在 django 会话中存储查询集以进行分页

过滤 Django 查询集以获取 dict 值

Django:保存旧的查询集以供将来比较

是否保证保留子查询中的顺序?

在更新之前评估查询集