如何正确查询列表(或另一个 ManyToManyField)中所有对象的 ManyToManyField?

Posted

技术标签:

【中文标题】如何正确查询列表(或另一个 ManyToManyField)中所有对象的 ManyToManyField?【英文标题】:How to properly query a ManyToManyField for all the objects in a list (or another ManyToManyField)? 【发布时间】:2010-12-22 22:50:56 【问题描述】:

我对构建 Django 查询的最佳方式感到困惑,该查询检查 所有 ManyToMany 字段(或列表)的元素是否存在于另一个 ManyToMany 字段中。

例如,我有几个Persons,他们可以拥有多个专长。也有Jobs 可供人们启动,但他们需要一个或多个Specialtys 才有资格启动。

class Person(models.Model):
    name = models.CharField()
    specialties = models.ManyToManyField('Specialty')

class Specialty(models.Model):
    name = models.CharField()

class Job(models.Model):
    required_specialties = models.ManyToManyField('Specialty')

一个人只有具备所有工作所需的专业才能开始工作。所以,再次举个例子,我们有三个专业:

编码 唱歌 跳舞

我有一个Job,需要唱歌和跳舞专业。一个有唱歌和跳舞特长的人可以开始,但另一个有编码和唱歌特长的人不能——因为这个工作需要一个既能唱歌又能跳舞的人。

所以,现在我需要一种方法来找到一个人可以从事的所有工作。这是我解决它的方法,但我相信还有更优雅的方法:

def jobs_that_person_can_start(person):
    # we start with all jobs
    jobs = Job.objects.all()
    # find all specialties that this person does not have
    specialties_not_in_person = Specialty.objects.exclude(name__in=[s.name for s in person.specialties])
    # and exclude jobs that require them
    for s in specialties_not_in_person:
        jobs = jobs.exclude(specialty=s)
    # the ones left should fill the criteria
    return jobs.distinct()

这是因为使用Job.objects.filter(specialty__in=person.specialties.all()) 将返回与该人的任何专长相匹配的工作,而不是所有专长。使用这个查询,需要唱歌和跳舞的工作会出现给唱歌的编码员,这不是想要的输出。

我希望这个例子不会太复杂。我担心这一点的原因是系统中的专长可能会更多,而循环遍历它们似乎不是实现这一目标的最佳方法。我想知道是否有人可以从头开始治疗这种痒!

【问题讨论】:

【参考方案1】:

另一个想法

好吧,我想我应该把这个添加到另一个答案中,但是当我开始使用它时,它似乎会是一个不同的方向哈哈

无需迭代:

person_specialties = person.specialties.values_list('pk', flat=True)

non_specialties = Specialties.objects.exclude(pk__in=person_specialties)

jobs = Job.objects.exclude(required_specialties__in=non_specialties)

注意我不知道这到底有多快。使用我的其他建议可能会更好。另外:此代码未经测试

【讨论】:

这段代码运行良好,而且明显比我的尝试快。非常感谢您的所有建议,我确实在这里学到了很多东西。 我将此答案标记为已接受,因为这正是我的问题所在;即使您的其他答案暗示了许多其他性能改进。【参考方案2】:

我认为您应该考虑使用values_list 来获取此人的特长

替换:

[s.name for s in person.specialties]

与:

person.specialties.values_list('name', flat=True)

这将为您提供一个简单的列表(即 ['spec1', 'spec2', ...]),您可以再次使用它。而且bg中使用的sql查询也会更快,因为它只会选择'name'而不是执行select *来填充ORM对象

您还可以通过过滤该人肯定无法执行的工作来提高速度:

所以替换:

jobs = Job.objects.all()

带有(2 个查询 - 适用于 django 1.0+)

person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)

or with (1 query? - 适用于 django1.1+)

jobs = Job.objects.filter(required_specialties__in=person.specialties.all())

您还可以通过在您的工作/人员查询中使用select_related() 来获得改进(因为他们有一个您正在使用的外键)

【讨论】:

使用 Django 1.1 你可以简单地做jobs = Job.objects.filter(required_specialties__in=person.specialties.all()) @Evgeny 我想了想,它的查询和我在这里的查询一样吗?我不确定 哇——非常感谢!遗憾的是没有一种“神奇”的方式来做到这一点,但你的建议在这里可以救命。 @Evgeny 根据 django 文档,使用子查询方法docs.djangoproject.com/en/dev/ref/models/querysets/#in 我认为这不会过滤只有“所有”品质的记录

以上是关于如何正确查询列表(或另一个 ManyToManyField)中所有对象的 ManyToManyField?的主要内容,如果未能解决你的问题,请参考以下文章

Mysql:优化使用一列或另一列的查询

如何使用 FilterUserMixin 过滤 Django 中的一个或另一个字段

如何创建一个 UIViewController 具有两个视图,根据单击的按钮显示一个或另一个

列表或单线阵列/参数:一个或另一个性能更好吗?

Access 中两个任务列表的 SQL 查询 - 如何获得正确的“分配给”?

如何根据Django Solr中选定的类别形成正确的查询列表