如何正确查询 ManyToManyField 中列表(或另一个 ManyToManyField)中的所有对象?
我对构建 Django 查询的最佳方法感到困惑,该查询检查 ManyToMany
字段(或列表)的所有元素是否存在于另一个 中ManyToMany
字段。
举个例子,我有几个 Person
,他们可以有多个专长。人们还可以开始一些工作
,但它们需要一项或多项专业
才有资格开始。
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')
一个人只有具备该工作所需的所有专长才可以开始工作。因此,再次举例,我们有三个专业:
- 编码、
- 唱歌、
- 跳舞
,而我有一个需要唱歌和跳舞专业的工作
。具有唱歌和跳舞特长的人可以开始,但具有编码和唱歌特长的人则不能——因为该工作需要一个既能唱歌又能跳舞的人。
所以,现在我需要一种方法来找到一个人可以承担的所有工作。这是我解决这个问题的方法,但我确信有一种更优雅的方法:
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()) 会返回以下作业:匹配此人的任何专长,而不是全部。使用此查询,将为唱歌编码器显示需要唱歌和跳舞的作业,这不是所需的输出。
我希望这个例子不会太复杂。我担心这一点的原因是系统中的专业可能会更多,而循环它们似乎并不是实现这一点的最佳方法。我想知道是否有人可以帮我止痒!
I'm rather stumped about the best way to build a Django query that checks if all the elements of a ManyToMany
field (or a list) are present in another ManyToMany
field.
As an example, I have several Person
s, who can have more than one Specialty. There are also Job
s that people can start, but they require one or more Specialty
s to be eligible to be started.
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')
A person can start a job only if they have all the specialties that the job requires. So, again for the sake of example, we have three specialties:
- Coding
- Singing
- Dancing
And I have a Job
that requires the Singing and Dancing specialties. A person with Singing and Dancing specialties can start it, but another with Coding and Singing specialties cannot -- as the Job requires a Person who can both sing and dance.
So, now I need a way to find all jobs that a person can take on. This was my way to tackle it, but I'm sure there's a more elegant approach:
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()
This is because using Job.objects.filter(specialty__in=person.specialties.all())
will return jobs that match any of the person's specialties, not all of them. Using this query, the job that requires Singing and Dancing would appear for the singing coder, which is not the desired output.
I'm hoping this example is not too convoluted. The reason I'm concerned about this is that the Specialties in the system will probably be a lot more, and looping over them doesn't seem like the best way to achieve this. I'm wondering if anyone could lend a scratch to this itch!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
另一个想法
好吧,我想我应该将其添加到另一个答案中,但是当我开始使用它时,它似乎会是一个不同的方向哈哈
不需要迭代:
注意:我不知道这到底有多快。我的其他建议可能会更好。
另外:此代码未经测试
Another Idea
Ok I guess I should have added this to the other answer, but when I started on it, it seemed like it was going to be a different direction haha
No need to iterate:
note: I don't know exactly how fast this is. You may be better off with my other suggestions.
Also: This code is untested
我认为你应该考虑使用 values_list获取此人的专长
将: 替换
为:
这将为您提供一个简单的列表(即 ['spec1','spec2',...]),您可以再次使用它。 bg 中使用的 sql 查询也会更快,因为它只会选择“name”,而不是执行
select *
来填充 ORM 对象。您还可以通过过滤以下作业来提高速度:人肯定不能执行:
所以替换:
with (2 个查询 - 适用于 django 1.0+)
或 with (1 个查询? - 适用于 django1.1+)
您还可以通过使用 select_lated() 在您的职位/人员查询上(因为它们有一个外键,您可以使用使用)
I think you should look at using values_list to get the person's specialties
Replace:
with:
That will give you a plain list (ie. ['spec1', 'spec2', ...]) which you can use again. And the sql query used in the bg will also be faster because it will only select 'name' instead of doing a
select *
to populate the ORM objectsYou might also get a speed improvement by filtering jobs that the person definately can NOT perform:
so replace:
with (2 queries - works for django 1.0+)
or with (1 query? - works for django1.1+)
You may also get an improvement by using select_related() on your jobs/person queries (since they have a foreign key that you're using)