【问题标题】:How to filter queryset on calculated field provided by `extra` call in Django?如何在 Django 中的“额外”调用提供的计算字段上过滤查询集?
【发布时间】:2010-12-13 01:59:01
【问题描述】:

这是我的 django 模型:

class Author (models.Model):
    name = models.CharField(max_length=255)
    removed = models.BooleanField(default=False)

class Image (models.Model):
    author = models.ForeignKey(Author)
    name = models.CharField(max_length=255)
    height = models.PositiveIntegerField()
    width  = models.PositiveIntegerField()

基本上,我需要选择每个未删除且具有 5 个或更少高度等于 100 的图像的作者。

我用的是 MySQL,这里是版本信息:

mysql Ver 14.12 Distrib 5.0.67

自然是这样的:

Author.objects.filter(removed=False).extra(select={
    'imgcount': """SELECT COUNT(*) 
                   FROM ormtest_image 
                   WHERE height=100 AND 
                         ormtest_image.author_id=ormtest_author.id"""
}).filter(imgcount__lte=5)

它不起作用:“FieldError: Cannot resolve keyword 'imgcount' into field。选项有:id、image、name、removed”

好的,我们试试额外方法的 where 参数:

Author.objects.filter(removed=False).extra(select={
    'imgcount': """SELECT COUNT(*) 
                   FROM ormtest_image 
                   WHERE height=100 AND 
                         ormtest_image.author_id=ormtest_author.id"""
}, where=['imgcount <= 5'])

它也不起作用:“OperationalError: (1054, "Unknown column 'imgcount' in 'where Clause'")”,因为要过滤 MySQL 中计算字段的数据,您必须使用 HAVING 子句。

有什么想法吗?

我使用 Django 1.1 和来自 trunk 的最新版本对此进行了测试。

到目前为止,我使用这个 hack:

Author.objects.filter(removed=False).extra(select={
    'imgcount': """SELECT COUNT(*) 
                   FROM ormtest_image 
                   WHERE height=100 AND 
                         ormtest_image.author_id=ormtest_author.id"""
}, where=['1 HAVING imgcount <=5'])

附: YAML 夹具:

---
- model: ormtest.author
  pk: 1
  fields:
      name: 'Author #1'
      removed: 0
- model: ormtest.author
  pk: 2
  fields:
      name: 'Author #2'
      removed: 0
- model: ormtest.author
  pk: 3
  fields:
      name: 'Author #3'
      removed: 1
- model: ormtest.image
  pk: 1
  fields:
      author: 1
      name: 'Image #1'
      height: 100
      width: 100
- model: ormtest.image
  pk: 2
  fields:
      author: 1
      name: 'Image #2'
      height: 150
      width: 150
- model: ormtest.image
  pk: 3
  fields:
      author: 2
      name: 'Image #3'
      height: 150
      width: 100
- model: ormtest.image
  pk: 4
  fields:
      author: 2
      name: 'Image #4'
      height: 150
      width: 150

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    好的,未经测试,因为我没有你的数据 - 这个怎么样:

    Author.objects.filter(removed=False).select_related('image').filter(image__height=100).annotate(count_of_images=Count('image')).filter(count_of_images__lte=5)
    

    编辑:

    这几乎带你到了那里。问题与外部连接有关......我认为这是应该为你做的最终版本:

    Author.objects.filter(removed=False).select_related('image').filter(Q(image__height=100) | Q(image__height__isnull=True)).annotate(count_of_images=Count('image')).filter(count_of_images__lte=5)
    

    里面的Q(image__height=100) | Q(image__height__isnull=True) 是诀窍。它将获取图片高度为 100 的作者的图片高度为 null(意味着他们没有关联的图片)。 p>

    PS。感谢您的提问...实际上比我最初想象的更具挑战性,并且在尝试测试它的过程中我学到了一些很酷的技巧!


    哎呀...我确实用 sqlite3 测试了我的最后一个解决方案。我没有用于测试的 MySQL 实例... :-(

    让我考虑一个替代方案。

    但是 - 是的 - 如果它在 sqlite 中工作,它应该在 MySQL 中工作;我会将其报告为错误。

    【讨论】:

    • 好吧,我需要从数据库中选择所有拥有 5 张或更少高度等于 100px 的图像的作者。使用注释,我将收到来自数据库的所有作者,这不是我需要的(出于性能原因)
    • 顺便说一句,您的解决方案仍在接收:OperationalError: (1054, "Unknown column 'imgcount' in 'where clause'")
    • 又编辑了一次,因为我忘记了图片的数量... :-)
    • 并再次编辑,因为我忘记了
    • 然而,这个解决方案消除了所有拥有 0 张高度 = 100 的图像的作者...
    【解决方案2】:

    据我所知,除了过滤大于 1 的作者之外,您没有使用 count 值。如果是这样,您可以通过一个简单的查询完全在 ORM 代码中执行此操作:

    Author.objects.filter(removed=False, image__height__gte=100)
    

    【讨论】:

    • 等等...但是 extra() 调用不是也将 imgcount 字段添加到查询集中吗?我以为他想要那个。如果他这样做的唯一原因是进一步限制查询集,那么您的解决方案显然是正确的。
    • 对不起,我提供的例子不是很好。在实际项目中,我需要获得拥有 5 个或更少“高度等于 100 的图像”的作者
    猜你喜欢
    • 2015-04-03
    • 1970-01-01
    • 2019-02-17
    • 1970-01-01
    • 2017-03-09
    • 2011-05-16
    • 2012-01-26
    • 2015-07-20
    • 1970-01-01
    相关资源
    最近更新 更多