我试图通过合并来自两个单独表的结果来获得最大值。我正在尝试获取帖子的最新评论。我有一个可以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}}
可以完成这样的自定义注释和加入吗?
答案 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将自动处理SELECT
和JOIN
语句的命名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`)
...
注意:这仅适用于上面的Post
和Comment
模型。我的实际实现有点复杂,需要这个解决方案。
如果Django团队开发了这个建议的补丁,在join
.extra()
中包含{{1}}语句,这也会容易得多:https://code.djangoproject.com/ticket/7231