使用 django ORM 获取只有最新孩子的父母的优化方式

Posted

技术标签:

【中文标题】使用 django ORM 获取只有最新孩子的父母的优化方式【英文标题】:Optimized way of fetching parents with only latest child using django ORM 【发布时间】:2011-05-30 14:29:26 【问题描述】:

我们希望以这样的方式获取父子节点,它可以为我提供最新的 10 位父节点,每个父节点只有一个最新的子节点记录。

例如:

Category
- id
- name
- created_date

Item
- id
- name
- category
- created_date

使用上面指定的模型结构,我想获取最新的 10 个类别以及每个类别的最新子项。

只要对服务器进行一次查询,我就想访问所有数据。

Category1.name, Category1.id, LatestItemForCat1.name, LatestItem1ForCat1.created_date
Category2.name, Category2.id, LatestItemForCat2.name, LatestItem1ForCat2.created_date
Category3.name, Category3.id, LatestItemForCat3.name, LatestItem1ForCat3.created_date

使用 django ORM 实现这一目标的优化方法是什么。

这可以通过以下 sql 查询来实现。我更喜欢使用 django ORM 来解决这个问题。或者更好的sql查询。

select c.name, c.id, i.name, i.created_date
from
    category c
inner join
    item i
on c.id = i.category_id
where i.id = (select id from item order by created_date desc limit 0,1)
limit 10

【问题讨论】:

你在这个问题上有很多标签。你想要一个 mysql 查询、一个 SQLite 查询还是一系列 Django 函数的答案? “只有一个最新的子记录”是否意味着您只想显示只有一个子项的类别,还是只需要获取最新的子项? 我已经编辑了问题以回答您的问题,如果您知道,请提供答案。 【参考方案1】:

您可以使用原始 SQL,并且您的查询看起来不错(只是在最后的限制之前缺少“按 c.created_date desc 排序”。

原始 SQL 解决方案中的子查询并不比在 Category 模型中返回最后 10 个条目的方法好多少,类似于以下(未经测试的)示例:

class Category(models.Model):
    ...
    def last10items(self):
        return self.item_set.order_by('-created_date')[:10]

我们说的是最多 100 条记录,我不会担心效率,因为后续查询就像命中缓存(有几层缓存:django、数据库和操作系统)。这样您就可以传递最后 10 个类别并从模板中调用 last10items:

% for category in categories %
  ...
  % for item in category.last10items %
      ...

我相信有些混蛋会因为我这样说而对我投反对票:除非你需要,否则不要尝试优化某些东西。在许多情况下,我很惊讶在分析代码之前,我对一种方法的效率比另一种方法有多么错误。阅读“Python Patterns - An Optimization Anecdote”。

【讨论】:

非常好的回复 Paulo,非常感谢,这是我对你的支持。但是我仍然觉得这个问题应该有一个更好的答案,在接受这个答案之前会再等一段时间。再次感谢。【参考方案2】:

如果您要获取每个类别的项目,则必须为每个类别访问数据库一次,因此最好反过来使用select_related,这将为您提供属于的类别没有额外命中的项目:

items = Item.objects.order_by('-category__created_date', '-created_date').select_related()
i = 0
cat = None
for item in items:
    if item.category != cat:
        print "%s: %s" % (item.category, item)
        cat = item.category
        i += 1
    if i == 10:
        break    

我添加了一个带有计数器的 for 循环,仅显示 10 个类别和一个项目,我认为如果这样做,我认为不可能通过 ORM 来限制它!

【讨论】:

感谢您的建议。关于这种方法,我真的不明白这是否是正确的方法。我不喜欢的是我们必须手动循环直到找到最新的 10 个类别。如果一个类别有 100 多个项目,想想会有多少次迭代? @software 热心:我仍然不确定我是否得到了正确的问题,如果我查看您现在发布的 sql:sql 不会给您最新的十个类别,但是十个最新项目及其类别(因此,如果它们具有相同的类别,则相同的类别将出现不止一次)。这是你想要的吗?如果是的话,我可以编辑我的帖子以适应这个! 是的,但这些类别应该是唯一的。根据您的代码,如果我们首先考虑项目及其相关类别,则要获得下一个唯一类别,我们将必须迭代、获取和跳过具有考虑类别的项目。

以上是关于使用 django ORM 获取只有最新孩子的父母的优化方式的主要内容,如果未能解决你的问题,请参考以下文章

使用谓词在 Core Data 中获取父母的所有孩子

Django orm 为每个组获取最新信息

如何在一个提交中保存父母、孩子和孙子表格 - django

Django ORM 获取每行的最新版本(内连接)

Django-cms 菜单模板标签问题

使用cheerio在没有孩子的父母中获取文本