合并表列中的Django最大值

时间:2012-05-29 11:16:17

标签: django join merge union annotate

我试图通过合并来自两个单独表的结果来获得最大值。我正在尝试获取帖子的最新评论。我有一个可以Comment访问的模型Post.comments。我还有一个与Post分开的Post.body可以由when访问。评论和正文都有一个DateTimeField字段class Body(models.Model): when = models.DateTimeField(default=datetime.datetime.now) class Post(models.Model): body = models.ForeignKey(Body) class Comment(models.Model): post = models.ForeignKey(Post, related_name='comments') when = models.DateTimeField(default=datetime.datetime.now) 。我想通过最近的活动返回一个已排序的查询集,以便首先显示具有最新评论或正文的帖子。

假设模型如下所示:

q = Post.annotate(
    activity=Max(
        Union('comments__when', 'body__when')
    )
)
q = q.order_by('-activity')

我希望结果在可能的情况下仍然是一个查询集,因为我继续处理它并进一步将其传递给一个paginator来限制结果。

基本上,我希望能够致电:

SELECT
...
IF(MAX(c.`when`), MAX(c.`when`), b.`when`) AS `activity`
...
FROM `post` p
...
LEFT OUTER JOIN `comment` AS c
ON c.`post_id`=p.`id`
LEFT OUTER JOIN `body` AS b
ON p.`body_id`=b.`id`
...

但我不知道如何完成那个联盟。

我相信完成我正在寻找的SQL可以与之相媲美:

{{1}}

可以完成这样的自定义注释和加入吗?

1 个答案:

答案 0 :(得分:0)

我花了很长时间才弄明白。一个大问题是Django不支持ON语句的JOIN子句的多个条件。由于单个SELECT语句依赖于两个单独的JOINs,因此我们在准确跟踪表名时遇到问题。当您要求django为表格添加注释时(例如Max()),最终会出现Max(T7.when) ... LEFT OUT JOIN table AS T7等条件,其中T7不一致。因此,在需要由Django自动生成的Max(T7.when)的情况下,我需要一种方法来积极地生成表达式JOINs。很多在线帖子都告诉您在查询集上使用.raw()支持,但在这种情况下使用ORM会失去很多好处。

我想出的解决方案是生成自定义聚合函数。我调用了这个函数CustomMax()

from django.conf import settings
from django.db import models

sum_if_sql_template = 'GREATEST(%(alt_table)s.%(alt_field)s, IFNULL(%(function)s(%(field)s), %(alt_table)s.%(alt_field)s))'

class CustomMaxSQL(models.sql.aggregates.Aggregate):
    sql_function = 'MAX'
    sql_template = sum_if_sql_template


class CustomMax(models.aggregates.Aggregate):
    name = 'Max'

    def __init__(self, lookup, **extra):
        self.lookup = lookup
        self.extra = extra

    def add_to_query(self, query, alias, col, source, is_summary):
        aggregate = CustomMaxSQL(col,
                             source=source,
                             is_summary=is_summary,
                             **self.extra)
        query.aggregates[alias] = aggregate

该功能的用法是:

q = Post.annotate(
    activity=CustomMax(
        'comments__when',
        alt_table="app_post",
        alt_field="when",
    )
)
q.query.join((
    'app_post',
    'app_comment',
    'id',
    'post_id',
))
q = q.order_by('-activity')

我包含.join()以允许alt_table作为JOIN存在,Django将自动处理SELECTJOIN语句的命名Max部分。此用法生成的SQL类似于:

SELECT 
...
GREATEST(app_post.when, IFNULL(MAX(T7.`when`), app_post.when)) AS `activity`,
...
`app_post`.`id`
...
INNER JOIN `app_post` ON ...
...
LEFT OUTER JOIN `app_comment` T7 ON (`app_post`.`id` = T7.`post_id`)
...

注意:这仅适用于上面的PostComment模型。我的实际实现有点复杂,需要这个解决方案。

如果Django团队开发了这个建议的补丁,在join .extra()中包含{{1}}语句,这也会容易得多:https://code.djangoproject.com/ticket/7231