【问题标题】:Django Maximum Value From Merged Table Columns来自合并表列的 Django 最大值
【发布时间】:2012-06-03 14:41:23
【问题描述】:

我试图通过合并两个单独表的结果来获得最大值。我正在尝试获取帖子的最新评论。我有一个模型Comment,可以通过Post.comments 访问。我还有一个与Post 分开的主体,Post.body 可以访问它。 cmets 和 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')

但我不知道如何实现这个联盟。

我相信完成我正在寻找的 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可以实现吗?

【问题讨论】:

    标签: django join merge union annotate


    【解决方案1】:

    我花了很长时间才弄清楚。一个大问题是 Django 不支持 JOIN 语句的 ON 子句的多个条件。由于单个 SELECT 语句依赖于两个单独的 JOINs,因此我们在准确跟踪表名时遇到了问题。当你让 django 注释一个表时,比如用Max(),你最终会得到Max(T7.when) ... LEFT OUT JOIN table AS T7 这样的条件,其中 T7 不一致。因此,考虑到 Django 自动生成的 JOINs,我需要一种方法来准确生成表达式 Max(T7.when)。许多在线帖子告诉您在查询集上使用.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 将自动处理Max 部分的SELECTJOIN 语句的命名。这种用法生成的 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 团队开发了这个建议的补丁,在.extra() 中包含join 语句,这也会容易得多:https://code.djangoproject.com/ticket/7231

    【讨论】:

      猜你喜欢
      • 2021-08-27
      • 2019-02-11
      • 1970-01-01
      • 2018-06-24
      • 2017-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多