假设我有一个团队模型,团队
有成员
。
所以
class Team(models.Model):
team_member = models.ManyToManyField('Employee')
class Employee(models.Model):
....
假设我有一个员工 ID 列表,例如 team_members = [1001, 1003, 1004]
,我想找到 Team
,它完全由这三个成员。
我不想要有 [1001, 1003, 1004, 1005]
的团队或有 [1001, 1003]
的团队。
只有团队 [1001, 1003, 1004]
。
这就是我现在正在做的:
teams = Team.objects.all()
for t in teams:
if set([x.id for x in t.team_member.all()]) == set(team_members):
team = t
if not team:
team = Team.objects.create()
team.team_member = team_members
但似乎有点笨手笨脚。有没有更简洁、嵌套循环更少的方法?
最佳答案
简短的回答
不,我不知道在代码外观方面有更简单的方法。
但是,您可以做一些事情来使您的代码更优雅,并可能更快。此外,可以在数据库中完成工作,尽管对于大型团队来说效率很低。
下面列出的 DB 选项与您提供的 for 循环一样笨手笨脚,但根据您的数据集、DB 等可能会更有效。
更长的答案:减少“笨手笨脚”的方法
这里有几个地方我要清理样式。
另外,根据我使用 Django 的经验,您构建的循环do 在大型数据集上往往会变得相当昂贵。如果您最终将 10,000 个团队加载到内存中,让 ORM 将它们转换为 Team
对象,然后迭代它们,您可能会看到一些明显的减速。
尝试速度和优雅的两件事:
- 将
Team.values_list('team_members')
用于您的 in-python 过滤器循环,它会跳过 Django 将所有 SQL 数据组织到Model
对象中的步骤。我发现这可以节省大量实例化对象的时间(有时大约节省一个数量级)。 - 整理您的
set()
调用。目前,您在每次迭代时都将team_members
重新转换为set()
,此外,您还将t.team_member
隐式转换为TeamMember
对象(因为它们是从数据库中获取的),然后放入id
的list
中,然后放入set
。对于第一项,只需在前面制作一个team_members_set = set(team_members)
并重用它。对于第二项,您可以执行set(t.team_member.values_list('id', flat=True))
这将跳过实例化TeamMember
的最重的 ORM 步骤(根据数据集和 Django 的缓存,这可能与示例中的O(n^2)
一样糟糕)。 - 使用Team.objects.all().iterator()不要一次将
Team
全部加载到内存中。如果您遇到内存问题,这会有所帮助。
但是对于任何性能优化,当然要使用真实或真实的数据测试您的性能,以确保您不会让事情变得更糟!
更长的答案:数据库选项
在尝试了各种Q()
操作和此处答案中列出的其他方法后,无济于事,我找到了 this answer by @Todor .
基本上您需要重复执行 filter()
,每个 team_member
一个。最重要的是,您使用 Count
过滤器来确保您最终不会选择包含所需成员超集的 Team
。
desired_members = [1001, 1003, 1004]
initial_queryset = Team.objects.annotate(cnt=models.Count('team_members')).filter(cnt=len(desired_members))
matching_teams = reduce( # Can of course use a for loop if you prefer that to reduce()
lambda queryset, member: queryset.filter(team_members=member),
desired_members,
initial_queryset
)
请注意,对于大型团队,生成的查询可能会出现性能问题,因为它会为您的每个 desired_members
执行一次 JOIN
。最好避免这种情况,但我不知道在不更改数据结构的情况下在数据库中执行所有操作的另一种方法。我很想学习更好的方法,如果您最终进行了一些性能测试,我很想知道您学到了什么!
关于Django 过滤精确的 m2m 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35804157/