【问题标题】:Django filter exact m2m objectsDjango过滤精确的m2m对象
【发布时间】:2016-06-18 16:00:09
【问题描述】:

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

所以

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

但这似乎有点笨拙。有没有更简洁的方法,嵌套循环更少?

【问题讨论】:

  • 我认为你可以使用in -> Team.objects.filter(team_member__pk__in=team_members)
  • 这将返回一个包含这三个成员和其他成员的团队。我想要这些团队成员。
  • 你试过了吗?那不会返回 a tem
  • 它返回一个包含任何team_members的团队的查询集。我需要一个只由这三个团队成员组成的团队。
  • 你说:如果有他们所属的。

标签: django django-models m2m


【解决方案1】:

要让团队拥有您可以使用的这三个确切成员:

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)

【讨论】:

  • 不:TypeError: int() argument must be a string or a number, not 'list'
  • 我猜这属于我答案的第一部分,正如我所说,该代码未经测试,请尝试第二部分。
  • 不,第二个返回与Team.objects.filter(team_member__pk__in=team_members) 相同的查询集,即所有将其中任何一个作为成员的团队,而不是他们的一个团队。
  • @RobL 这是一个get() 查询,它不能返回多个结果。
【解决方案2】:

也许您可以使用 annotate 来计算 team_member 的数量。你可以试试这个吗?

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

【讨论】:

  • 好的,行得通!在修复了小语法错误(Count(team_member) -> Count('team_member') 之后,慢得多。在非常小的数据库上,它需要 14.5 毫秒,而 .all() 查询需要 0.3 毫秒。
  • 抱歉语法错误,因为我没有机会测试代码。我刚刚看到了下面解决方案的一部分,它给出了所有已经给成员的团队。我刚刚添加了代码的注释部分。如果实在是太慢了,我们可以考虑另一种解决方案。
  • 不会太慢,真的很酷。只是不相信它更具可读性。
  • 嗯,好吧 :) 不要担心其他任何事情,除非它不能总是给你你想要的。
  • 这不是让团队与任何团队成员一起,然后计算团队以与团队计数进行比较吗?因此,它会返回所有拥有成员之一且成员数量与“team_members”相同的团队,但不返回拥有这些成员且仅拥有这些成员的团队。
【解决方案3】:

简短的回答

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

但是您可以做一些事情来使您的代码更优雅一点,并且可能更快。另外,可以在数据库中完成工作,尽管对于大型团队来说效率很低。

下面列出的 DB 选项与您提供的 for 循环几乎一样笨拙,但可能更有效,具体取决于您的数据集、DB 等。

更长的答案:减少“笨手笨脚”的方法

这里有几个地方我会清理样式。

另外,根据我使用 Django 的经验,像您构建的循环 do 在大型数据集上往往会变得非常昂贵。如果您最终将 10,000 个团队加载到内存中,让 ORM 将它们转换为 Team 对象,然后对它们进行迭代,您可能会看到明显的减速。

为了速度和优雅而尝试两件事:

  1. Team.values_list('team_members') 用于您的python 内过滤循环,这将跳过Django 将所有SQL 数据组织到Model 对象中的步骤。我发现这可以节省大量实例化对象的时间(有时大约一个数量级)。
  2. 理顺您的set() 电话。目前,您在每次迭代时都将team_members 重新转换为set(),并且您将t.team_member 隐式转换为TeamMember 对象(因为它们是从数据库中获取的),然后转换为list ids 然后变成set。对于第一项,只需在前面创建一个team_members_set = set(team_members) 并重复使用它。对于第二项,您可以执行set(t.team_member.values_list('id', flat=True)),这将跳过实例化TeamMembers 的最繁重的ORM 步骤(根据数据集和Django 的缓存,这可能与您的示例中的O(n^2) 一样糟糕)。
  3. 使用Team.objects.all().iterator() 不会一次将Teams 全部加载到内存中。如果您遇到内存问题,这将有所帮助。

但是,通过任何性能优化,当然要使用真实或真实的数据来测试您的性能,以确保您不会让事情变得更糟!

更长的答案:DB 选项

在尝试了各种Q() 操作和此处答案中列出的其他方法后,无济于事,我找到了this answer by @Todor

基本上你需要重复filter()s,每个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。避免这种情况会很好,但我不知道在不改变数据结构的情况下在数据库中完成这一切的另一种方法。我很想学习一种更好的方法,如果你最终做一些性能测试,我很想知道你学到了什么!

【讨论】:

  • 是的,这个答案(我相信它比这个问题更新)看起来可行。但我不确定它是否比我正在做的更好。
  • 好点,我更新了答案以更直接地回答您的问题(不仅仅是我之前回答的面向 DB 的答案)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-15
  • 2012-11-06
  • 1970-01-01
  • 1970-01-01
  • 2018-08-05
  • 1970-01-01
  • 2017-01-07
相关资源
最近更新 更多