【问题标题】:Limit queryset results based on model field, are there better ways?根据模型字段限制查询集结果,有更好的方法吗?
【发布时间】:2025-12-29 14:55:09
【问题描述】:

基本上我想为每个主机获取最新的 30 个日志条目。目前我正在 django-piston 中执行此操作。

def read(self,request):
    val={}
    for x in Host.objects.all():
        val[x.uuid_id]=DataLog.objects.filter(host=x).order_by('-time')[:30]           
    return val

不幸的是,这个请求需要很长时间(目前大约 10k 数据库条目需要 1 秒)。 有没有更有效的方法来做到这一点?

哈珀

【问题讨论】:

  • 这是一个常见问题,没有原始 SQL 的单一查询解决方案。查找“Top N GROUP BY”以查找有关 SQL 的帮助。当我的常见问题解答需要每个类别的前 N ​​个问题时,我首先编写原始 SQL,然后在代码更改后决定放弃这个想法并只缓存结果:)
  • 如何将每个主机的最后 30 行保存在不同的表中(例如,LatestDataLog)?在创建数据记录时使用信号,将数据记录 ID 保存在 LatestDataLog 中并清除超过 30 行的主机记录。
  • 我认为你的标题选错了。我想问“如何按类别获取前 N 个条目”

标签: python django django-queryset django-piston


【解决方案1】:

如果您使用 PostgreSQL 作为数据库后端并且不需要跨数据库兼容性,则可以使用允许类似操作的 powerful window functions

想象一下你的桌子是这样的:

CREATE TABLE x (
    i serial primary key,
    value integer not null,
    date timestamp,
    category text);

您需要每个类别的最新值。你会做的:

SELECT
    first_value(i) over w,
    first_value(value) over w,
    first_value(date) over w
    category,
FROM x
WINDOW w AS (PARTITION BY category ORDER BY date DESC);

您可以通过查询集管理器上的raw 方法在 django 中使用这样的查询:

ModelX.objects.raw("""SELECT DISTINCT ....... FROM x WINDOW w .....""")

要按类别获取最后 N 个条目,查询稍微复杂一些,涉及到一个子查询:

SELECT i, value, date, category
FROM (SELECT
        i, value, date, category,
        row_number() over w
    FROM x
    WINDOW w AS (PARTITION BY category ORDER BY date DESC)) AS subquery
WHERE subquery.row_number <= 30;

看到这个,你甚至可以做一个视图:

CREATE VIEW x_with_reverse_date_index AS
    (SELECT
        i, value, date, category,
        row_number() over w
    FROM x
    WINDOW w AS (PARTITION BY category ORDER BY date DESC));

并创建一个查询该视图的 django 模型:

class ModelX(models.Model):
    ...
    ...
    row_number = models.IntegerField("Row number when ordering by date desc")

    class Meta:
        db_table = 'x_with_reverse_date_index'

并“正常”查询:

ModelX.objects.filter(category__in = ('catA','catB'), row_number__lte = 30)
ModelX.objects.filter(row_number = 29)
...

警告:同样,如果您需要在另一个数据库引擎上工作的代码,请不要这样做。

【讨论】: