Django:按子对象属性排序

3 投票
5 回答
2890 浏览
提问于 2025-04-15 15:45

考虑一下这些模型:

class Author(models.Model):
    name = models.CharField(max_length=200, unique=True)

class Book(models.Model):
    pub_date = models.DateTimeField()
    author = models.ForeignKey(Author)

现在假设我想按照出版日期来给所有书籍排序。我会使用 order_by('pub_date')。但是如果我想要一个按最近出版书籍的作者排序的列表呢?

其实这很简单,想一想就明白了。基本上就是:

  • 排在最前面的作者是最近出版书籍的那位
  • 接下来的是出版书籍时间稍微久一点的作者
  • 依此类推。

我可能可以拼凑出一个解决方案,但因为这个列表可能会变得很大,我需要确保我这样做是正确的。

感谢你的帮助!

补充:最后,给每个作者加一个新字段来显示他们最后一本书的出版日期,并一直更新这个字段,这样做会不会更好呢?

5 个回答

1

或者,你可以试试这样的写法:

Author.objects.filter(book__pub_date__isnull=False).order_by('-book__pub_date')

3
from django.db.models import Max
Author.objects.annotate(max_pub_date=Max('books__pub_date')).order_by('-max_pub_date')

这要求你使用 Django 1.1 版本。

我假设你会在书籍模型(Book model)中的作者字段(author field)添加一个叫做 'related_name' 的东西,这样你就可以通过 Author.books 来调用,而不是用 Author.book_set。这样看起来更容易理解。

0

最后,给每个记录加一个新字段来显示最后一本书的日期,并且一直更新这个字段,这个方法会不会更好呢?

其实会的!这是一种常见的“反规范化”做法,可以这样实现:

class Author(models.Model):
    name = models.CharField(max_length=200, unique=True)
    latest_pub_date = models.DateTimeField(null=True, blank=True)

    def update_pub_date(self):
        try:
            self.latest_pub_date = self.book_set.order_by('-pub_date')[0]
            self.save()
        except IndexError:
            pass # no books yet!

class Book(models.Model):
    pub_date = models.DateTimeField()
    author = models.ForeignKey(Author)

    def save(self, **kwargs):
        super(Book, self).save(**kwargs)
        self.author.update_pub_date()

    def delete(self):
        super(Book, self).delete()
        self.author.update_pub_date()

这是你除了之前提到的两个选项之外的第三个常见选择:

  • 用SQL进行连接和分组
  • 把所有书籍数据拿到Python里,然后去掉重复的

这两个选项都是在你读取数据时,从规范化的数据中计算出版日期。而反规范化是在你写入新数据时,针对每个作者进行计算。这样做的想法是,大多数网页应用程序读取数据的频率远高于写入数据,所以这种方法更受欢迎。

这种方法的一个被认为的缺点是,你基本上在不同的地方存储了相同的数据,这就需要你保持这些数据的一致性。通常这让数据库专家感到很头疼 :-)。但通常在你使用ORM模型处理数据时,这并不是个问题(你可能本来就会这样做)。在Django中,是应用程序控制数据库,而不是相反。

另一个(更现实的)缺点是,像我展示的那种简单代码在大量更新书籍时可能会变得非常慢,因为每次更新都要去联系作者更新他们的数据。这通常通过设置一个标志来暂时禁用调用 update_pub_date,然后再手动调用来解决。总的来说,反规范化的数据需要比规范化的数据更多的维护。

撰写回答