我在数据库模型的图表上更一般地解释它。它可以应用于任何具有额外内容的“GROUP BY”。
+-------------------------+
| MovieTicket (booked_at) |
+-----+--------------+----+
| |
+---------+--------+ +--+---+
| Show (time) | | User |
++----------------++ +------+
| |
+------+-------+ +-----+------+
| Movie (name) | | Day (date) |
+--------------+ +------------+
问题是:如何总结由用户(其他相关对象)过滤的 Show(一个相关对象)分组的 MovieTicket(最顶层对象)以及来自一些相关更深层对象(电影和天)并按组从最顶层模型聚合的某个字段对这些结果进行排序(按组中最近 MovieTicket 的预订时间):
通过更一般的步骤解释答案:
- 从最上面的模型开始:
(MovieTicket.objects ...)
- 应用过滤器:
.filter(user=user)
- 按
pk 对最近的相关模型进行分组很重要(至少是那些未被过滤器保持不变的模型) - 它只是“显示”(因为“用户”对象仍被过滤到一个用户)
.values('show_id')
即使所有其他字段一起是唯一的(show__movie__name、show__day__date、show__time),数据库引擎优化器最好按 show_id 对查询进行分组,因为所有这些其他字段都依赖于 show_id 并且不会影响组的数量。
- 注释必要的聚合函数:
.annotate(total_tickets=Count('show'), last_booking=Max('booked_at'))
- 添加必需的依赖字段:
.values('show_id', 'show__movie__name', 'show__day__date', 'show__time')
- 按需要排序:
.order_by('-last_booking')(从最新到最旧降序)
在没有通过聚合函数封装的情况下,不输出或排序最顶层模型的任何字段是非常重要的。 (Min 和 Max 函数非常适合从组中采样。未通过聚合封装的每个字段都将添加到“分组依据”列表中,这将打破预期的组。朋友的同一个节目的更多门票可能是逐步预订,但应一起计算并按最新预订报告。)
放在一起:
from django.db.models import Max
qs = (MovieTicket.objects
.filter(user=user)
.values('show_id', 'show__movie__name', 'show__day__date', 'show__time')
.annotate(total_tickets=Count('show'), last_booking=Max('booked_at'))
.order_by('-last_booking')
)
查询集可以很容易地转换为 JSON 如何在他的回答中演示 zaphod100.10,或者直接以这种方式为对 django-rest 框架不感兴趣的人展示:
from collections import OrderedDict
import json
print(json.dumps([
OrderedDict(
('show', x['show_id']),
('movie', x['show__movie__name']),
('time', x['show__time']), # add time formatting
('day': x['show__day__date']), # add date formatting
('total_tickets', x['total_tickets']),
# field 'last_booking' is unused
) for x in qs
]))
验证查询:
>>> print(str(qs.query))
SELECT app_movieticket.show_id, app_movie.name, app_day.date, app_show.time,
COUNT(app_movieticket.show_id) AS total_tickets,
MAX(app_movieticket.booked_at) AS last_booking
FROM app_movieticket
INNER JOIN app_show ON (app_movieticket.show_id = app_show.id)
INNER JOIN app_movie ON (app_show.movie_id = app_movie.id)
INNER JOIN app_day ON (app_show.day_id = app_day.id)
WHERE app_movieticket.user_id = 23
GROUP BY app_movieticket.show_id, app_movie.name, app_day.date, app_show.time
ORDER BY last_booking DESC
注意事项:
模型图类似于 ManyToMany 关系,但 MovieTicket 是单独的对象,可能包含座位号。
一个查询很容易为更多用户获得类似的报告。字段“user_id”和名称将添加到“values(...)”中。
相关模型 Day 并不直观,但很明显它有一个字段 date 并且希望还有一些非平凡的字段,对于安排与电影假期等事件相关的节目可能很重要。将字段“日期”设置为 Day 模型的主键并在许多类似这样的查询中频繁地进行关系查找会很有用。
(此答案的所有重要部分都可以在最旧的两个答案中找到:Todor 和 zaphod100.10。不幸的是,这些答案没有组合在一起,然后除了我之外的任何人都没有投票,即使这个问题有很多赞成票。)