1

假设我有这个模型:

class PhotoAlbum(models.Model):
    title = models.CharField(max_length=128)
    author = models.CharField(max_length=128)

class Photo(models.Model):
    album = models.ForeignKey('PhotoAlbum')

我想做这个查询:“找到 10 个名字以'The'开头的相册,然后给我这些相册中的所有照片。”

在 SQL 中,我可以这样做:

SELECT * FROM
    (SELECT * FROM photoalbum WHERE title LIKE 'The%' LIMIT 10) AS selected_albums
LEFT JOIN photo ON photo.album_id = selected_albums.id

我的问题是,我怎样才能在 Django 中做到这一点?(不触发对每个专辑的查询!)我认为这是一个相当普遍的要求,我不敢相信没有办法做到这一点。

如果没有 Django-ey 方式,我将满足于“如何使用原始 SQL 在 Django 中实现它?”。

以下是一些不起作用的事情:

  • select_related(); 那是向前 ForeignKey的关系,这是向后的。
  • prefetch_related(); 也适用于前向关系。编辑:实际上这确实有效!至少对于一个级别的ForeignKeys。
  • PhotoAlbum.photo_set; 触发对每个专辑的查询。
  • 我得到的最接近的是:

    相册 = PhotoAlbum.objects.all()[:10] 照片 = Photo.objects.filter(album__in=albums)

但遗憾的是,它在 MySQL 上不起作用,我被告知使用LEFT JOIN's 比使用WHERE ... IN (SELECT ...)它创建的类型查询更好。

编辑

我发现了一个关于这个问题的 3 岁邮件列表帖子。其中没有解决方案。

一个6 年前的错误报告说他们不会修复它。除了“这不是它的工作方式”之外,没有给出任何理由。显然,在 RoR 中是可能的。

4

3 回答 3

4

在 Django 1.4+ 中,您可以使用prefetch_related

PhotoAlbum.objects.filter(title__startswith='The').prefetch_related('photo')[:10]

在较小的版本中尝试django-batch-select

更新

对不起。我主要还是在1.3上,所以我用prefetch_related的不多。在所有其他查询类型中,您不包括_set附录,但 Django 显然在这里打破了约定。如果你使用它会工作prefetch_related('photo_set')

如果您需要获取多个内容,您可以像使用 一样列出字段select_related,即:

prefetch_related('something', 'something_else', 'foo')

但是请密切注意文档中的这一部分:

还请记住,与 QuerySet 一样,任何暗示不同数据库查询的后续链接方法都将忽略先前缓存的结果,并使用新的数据库查询检索数据。因此,如果您编写以下内容:

   >>> pizzas = Pizza.objects.prefetch_related('toppings')
   >>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

...那么,pizza.toppings.all() 已经被预取的事实对你没有帮助——事实上它会损害性能,因为你已经完成了一个你没有使用过的数据库查询。所以请谨慎使用此功能!

于 2012-07-31T18:07:53.383 回答
1

这将起作用,并且只执行两个查询。prefetch_related 适用于反向 FK,这实际上是它的创建目的:

for album in PhotoAlbum.objects.filter(title__startswith='The').prefetch_related('photo_set')[:10]:
    print album.photo_set.all()
于 2012-07-31T20:30:58.413 回答
0

您不想在所有 PhotoAlbums 上触发 photo_set,是吗?只是指定的?

for p in PhotoAlbum.objects.filter(title__startswith='The'):
    p.photo_set.all()
于 2012-07-31T20:06:43.137 回答