1

我试图通过合并两个单独表的结果来获得最大值。我正在尝试获取帖子的最新评论。我有一个模型Comment可以通过Post.comments. 我还有一个与 .Post可以访问的主体分开的主体Post.body。评论和正文都有一个字段whenDateTimeField. 我想按最近的活动返回一个排序的查询集,以便首先显示具有最新评论或正文的帖子。

假设模型如下所示:

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')

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

我相信完成我正在寻找的 SQL 类似于:

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`
...

这样的自定义注解和Join可以实现吗?

4

1 回答 1

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模型。我的实际实现有点复杂,需要这个解决方案。

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

于 2012-05-30T10:22:29.427 回答