Django过滤精确的m2m对象

时间:2016-03-04 19:08:48

标签: django django-models m2m

假设我有一个团队模型,teammember s。

所以

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

但它看起来有点像火腿。是否有更简洁的方法,嵌套循环更少?

3 个答案:

答案 0 :(得分:1)

简短回答

不,我不知道在代码外观方面更简单的方法。

此外, 可以在数据库中完成工作,尽管对于大型团队规模来说效率非常低。

下面列出的数据库选项与您提供的for循环一样,但可能更高效,具体取决于您的数据集,数据库等。

更长的答案:减少'火腿'的方法

有几个地方我会在这里清理这个风格。

另外,根据我对Django的体验,你构建的之类的循环往往在大型数据集上变得相当昂贵。如果您最终将10,000个团队加载到内存中,让ORM将它们转换为Team个对象,然后迭代它们,您可能会看到一些显着的减速。

速度和速度要尝试两件事恩:

  1. 使用Team.values_list('team_members')进行in-python过滤器循环,跳过Django将所有SQL数据组织到Model个对象中的步骤。我发现这可以节省大量实例化对象的时间(有时大约一个数量级)。
  2. 理顺set()来电。目前,您在每次迭代时都会将team_members重新转换为set(),而且您将t.team_member隐式转换为TeamMember个对象(因为它们是从数据库中提取的) ),然后进入list id,然后进入set。对于第一项,只需预先设置team_members_set = set(team_members)并重复使用。对于第二个项目,您可以执行set(t.team_member.values_list('id', flat=True)),这将跳过实例化TeamMember的最重的ORM步骤(根据数据集和Django的情况,这可能与示例中的O(n^2)一样糟糕。高速缓存)。
  3. 使用Team.objects.all().iterator()不会立即将Team全部加载到内存中。如果您遇到内存问题,这将有所帮助。
  4. 但是,通过任何性能优化,当然可以使用真实或真实的数据测试你的性能,以确保你没有让事情变得更糟!

    更长的答案:数据库选项

    在尝试了各种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
    )
    

    请注意,生成的查询可能会为大型团队带来性能问题,因为它会为您的JOIN中的每一个执行一次desired_members。避免这种情况会很好,但我不知道在不改变数据结构的情况下在数据库中执行此操作的另一种方法。我很想学习更好的方法,如果你最终做了一些性能测试,我很想知道你学到了什么!

答案 1 :(得分:-1)

也许你可以使用注释来计算team_member的数量。你能试试吗?

Team.objects.filter(team_member__pk__in=team_members).annotate(num_team=Count('team_member')).filter(num_team=len(team_members))

答案 2 :(得分:-2)

为了让团队能够使用以下三个成员:

Team.objects.get(team_member__pk=team_members)  # This code was untested

您还可以尝试使用Employee个对象列表:

# team_members = Employee.objects.filter(pk__in=tem_members)

team_members = [<Employee: Employee object>, <Employee: Employee object>, <Employee: Employee object>]

Team.objects.get(team_member=team_members)