Django 1.9:为 QuerySet 创建复杂的自定义过滤器方法

Posted

技术标签:

【中文标题】Django 1.9:为 QuerySet 创建复杂的自定义过滤器方法【英文标题】:Django 1.9: Create complex custom filter method for QuerySet 【发布时间】:2016-06-21 02:42:22 【问题描述】:

目标是创建一个custom_filter 方法,该方法可以链接到标准的 Django 过滤器方法。 custom_filter 方法可能需要一些原始 SQL 代码。在最好的情况下,QuerySet 仍然会被评估为惰性。

最后,这样的命令会很棒:

apple_query_set = Apple.objects.filter(<any standard filtering>).custom_filter()

这是模型:

class Apple(models.model):
    a = models.IntegerField()
    b = models.IntegerField()
    date = models.DateField()

custom_filter 的目标是按 (a,b) 对 Apple 实例进行分组,并为每个 group 根据date只返回最新的实例。

此类过滤器的原始 SQL 代码如下:

custom_filter_raw_sql = """
SELECT t1.id
FROM app_apple AS t1
INNER JOIN (SELECT a, b, max(date) AS max_date
            FROM app_apple
            GROUP BY a, b) AS t2
ON t1.a = t2.a AND t1.b = t2.b AND t1.date = t2.max_date;
"""

到目前为止,为了添加custom_filter 功能, 我已经尝试(不成功)将objects = AppleQuerySet.as_manager() 添加到 Apple 类中,其中:

 class AppleQuerySet(models.QuerySet):
    def custom_filter(self):
        subquery = """
        SELECT t1.id
        FROM app_apple AS t1
        INNER JOIN (SELECT a, b, max(date) AS max_date
                    FROM app_apple
                    GROUP BY a, b) AS t2
        """
        condition = "t1.a = t2.a AND t1.b = t2.b AND t1.date = t2.max_date"
        return self.extra(tables=[subquery], where=[condition])

但是,我不确定这种方法是否可行,因为自定义查询 不仅应该适用于所有 Apple 实例 (Apple.objects.),而且应该可以将其链接到过滤后的查询集 (Apple.objects.filter())

创建此自定义可链接(惰性)custom_filter 功能的最佳方法是什么?我哪里错了?非常感谢!

【问题讨论】:

是否有特定原因不能通过使用查询本身的所有过滤条件直接perform raw queries? @AKS 这可能是一个解决方案,但我不知道如何确保 SQL 代码可以任意链接到 QuerySet(而不是对整个 Apple 表进行过滤)。我很高兴看到一些代码如何做到这一点。 (另外,我不确定这个解决方案是否允许惰性评估。) 【参考方案1】:

这是另一种方式,但我想知道您是否只能使用order_bydistinct的组合来达到预期的效果:

Apple.objects.order_by('a', 'b', '-date').distinct('a', 'b')

如果您在 order_bydistinct 中保持相同的字段顺序,则此组合有效。

而且,如果需要,您还可以预先使用链接的filter

一些解释:

使用following只会将所有具有相似ab的对象放在一起

Apple.objects.order_by('a', 'b')

但是,您可以通过-date(按降序排列)进一步对组中的对象(具有相同的ab 值)进行排序

Apple.objects.order_by('a', 'b', '-date')

现在,所有具有相似ab 的对象都在一起,并且每个组中的第一个元素具有最新的date。因此,我们可以使用distinct('a', 'b') 将它们保持在顶部

Apple.objects.order_by('a', 'b', '-date').distinct('a', 'b')

【讨论】:

这是一个优雅的答案! @AKS,您认为(在这种情况下当然)通过自定义 SQL 添加过滤器链步骤的笨拙方法在技术上是可行的吗?以防万一,某处是否有示例代码? 谢谢@elke,你坚持你提到的方法有什么特别的原因吗?我这样说是因为使用上述现有的 django 功能可以轻松实现您想要的结果。 @AKS,对于上面的确切示例,您的方法绝对是首选。我的想法是,如果另一个用户偶然发现这篇文章,“如何将类似复杂的 sql 查询插入到查询集过滤器”的答案可能是她要搜索的,而不是我特定示例的解决方案问题。然而,也许所有的问题最终都有这么优雅的解决方案?【参考方案2】:

我认为您需要的是自定义管理器。查看Django documentation

您可以在这里看到一个使用原始 SQL 代码的示例:

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute("""
            SELECT p.id, p.question, p.poll_date, COUNT(*)
            FROM polls_opinionpoll p, polls_response r
            WHERE p.id = r.poll_id
            GROUP BY p.id, p.question, p.poll_date
            ORDER BY p.poll_date DESC""")
        result_list = []
        for row in cursor.fetchall():
            p = self.model(id=row[0], question=row[1], poll_date=row[2])
            p.num_responses = row[3]
            result_list.append(p)
        return result_list

【讨论】:

如果返回值应该是 QuerySet,这个解决方案会是什么样子?该方法是否可以作为链式过滤器应用(而不是在整个 Apple 表上重新运行查询?)。此解决方案是否允许延迟评估? docs.djangoproject.com/es/1.9/topics/db/managers/… 这里展示了如何从自定义管理器创建自定义查询集,它应该回答前两个问题。我不知道“允许延迟评估”是什么意思

以上是关于Django 1.9:为 QuerySet 创建复杂的自定义过滤器方法的主要内容,如果未能解决你的问题,请参考以下文章

是否可以通过检查它们的交集是否为空来过滤 django 多对多 QuerySet 字段?

Django - 如何将 QuerySet 转换为 Q 对象?

刷新单应用程序 django 1.9

Django--QuerySet

Django:防止模型上的 QuerySet 删除?

django的queryset和objects对象