Django Querysets - 关系匹配所有子查询或在第二个过滤器

时间:2016-03-18 00:28:11

标签: python django django-queryset django-filter

我正在尝试为模型构建一个Django查询集,其中所有模型的关系都由一些子查询或子查询表示。 例如,乐队通过会员资格拥有音乐家和乐器。我想找到两个成员弹吉他的所有乐队,或者其中一个成员弹吉他而另一个成员演奏鼓。

我最接近的是提出这个相似但不太正确的问题的建议之一:Django filter queryset __in for *every* item in list

可能更容易解释我正在尝试做什么以及为什么上述解决方案无法用于示例。

型号:

class Instrument(models.Model):
    name = models.CharField(max_length=255)


class Musician(models.Model):
    name = models.CharField(max_length=255)


class Band(models.Model):
    name = models.CharField(max_length=255)
    musicians = models.ManyToManyField(Musician, through='Membership')
    instruments = models.ManyToManyField(Instrument, through='Membership')


class Membership(models.Model):
    band = models.ForeignKey(Band, on_delete=models.CASCADE, related_name='memberships')
    musician = models.ForeignKey(Musician, on_delete=models.CASCADE, related_name='memberships')
    instrument = models.ForeignKey(Instrument, on_delete=models.CASCADE, related_name='memberships')

数据:

Instruments
id | name
 1 | Guitar
 2 | Drums
 3 | Keyboard
 4 | Vocals

Musicians
id | name
 1 | John
 2 | Paul
 3 | George
 4 | Ringo

Bands
id | name
 1 | The Beatles
 2 | The Beadles
 3 | The Beagles

Memberships
id | band_id | musician_id | instrument_id
 1 |       1 |           1 |             1
 2 |       1 |           2 |             1
 3 |       2 |           1 |             1
 4 |       2 |           2 |             1
 5 |       3 |           1 |             1
 6 |       3 |           2 |             2

稍微易消化的形式有三个带,每个带有两个成员/乐器。他们是:

The Beatles: John/Guitar, Paul/Guitar
The Beadles: John/Guitar, Paul/Guitar
The Beagles: John/Guitar, Paul/Drums

问题:

第1部分 - 工作示例 我想找到两个成员弹吉他的所有乐队。

guitar = Instrument.objects.get(name='Guitar')

guitar_memberships_1 = Membership.objects.filter(instrument=guitar)
guitar_memberships_2 = Membership.objects.filter(instrument=guitar)
guitar_memberships = guitar_memberships_1 | guitar_memberships_2
# Yes, all three of these querysets are equal. I'm writing my code 
# this way because I don't always know that they will be equal,
# and because I specifically want bands where TWO members play guitar.
# Hopefully this will be more clear in the part 3 of my example.

Band.objects.filter(memberships__in=guitar_memberships).annotate(num_mems=Count('memberships')).filter(num_mems=2).count()
# num_mems=2 - in reality, 2 might be replaced with something like len(memberships), 
# where memberships is a list of querysets

这是有效的,或者至少看起来如此。它返回2,因为有两个乐队,其中两个成员弹吉他。

第2部分 - 这不起作用 我想找到至少有一个成员弹吉他的所有乐队。

guitar = Instrument.objects.get(name='Guitar')

guitar_memberships = Membership.objects.filter(instrument=guitar)
Band.objects.filter(memberships__in=guitar_memberships).annotate(num_mems=Count('memberships')).filter(num_mems=1).count()

这将返回1 - The Beagles,其中只有一个成员弹吉他

我希望它返回3,因为有三个乐队,其中至少有一个成员弹吉他。

第3部分 - 这也不起作用 我想找到所有乐队,其中一个成员弹吉他,一个成员演奏鼓

guitar = Instrument.objects.get(name='Guitar')
drums = Instrument.objects.get(name='Drums')

# This ultimately uses the same process as the example in part 1, but is closer to my actual code
memberships = []
memberships.append(Membership.objects.filter(instrument=guitar))
memberships.append(Membership.objects.filter(instrument=drums))
mem_queryset = Instrument.objects.none()
for mem in memberships:
    mem_queryset = mem_queryset | mem

Band.objects.filter(memberships__in=mem_queryset).annotate(num_mems=Count('memberships')).filter(num_mems=len(memberships)).count()

与示例1不同,这不起作用。它返回3.

在这种情况下,

len(成员资格)解析为2。所以它包括The Beagles,因为有两个成员的成员资格在合并的查询集中,但是有两个吉他手的乐队也是如此。

如果它有帮助,这里有一些原始的SQL可以实现我想要的东西:

SELECT DISTINCT b.*
FROM bands_band b
INNER JOIN bands_membership m1 ON m1.band_id = b.id AND m1.instrument_id = 1
INNER JOIN bands_membership m2 ON m2.band_id = b.id AND m2.id != m1.id AND m2.instrument_id = 2;

对于它的价值,我也有一个使用子选项工作的查询,但我不记得我是怎么做的,并且无法重新创建它。它同样取决于将第二个成员资格的id与第一个成员资格的id进行比较,以避免相同成员资格被连接两次的频段。我一直无法使用Django查询集重新创建这种方法,也就是说,我找不到从第一个连接表的过滤器中的一个连接表中引用字段的方法,所以我不能{{{ 1}}

显然,我宁愿避免使用原始SQL,如果可能的话,我也想避免使用extra(),因为似乎Django计划在某些时候弃用它

@karthikr: 以下是我使用您编辑中建议的方法进行的一些尝试。

m2.id != m1_id

guitar = Instrument.objects.get(name='Guitar') drums = Instrument.objects.get(name='Drums') memberships = [] memberships.append(Membership.objects.filter(instrument=guitar)) memberships.append(Membership.objects.filter(instrument=drums)) bands = Band.objects.filter(reduce(operator.and_, (Q(memberships__in=x) for x in memberships))) bands = bands.annotate(num_mems=Count('memberships')).filter(num_mems__gte=len(memberships)) 返回0,打印bands.count()会返回:

bands.query

从查询中可以更清楚地知道它在这里返回0,因为没有bands_membership.id同时存在于这两个子选择中。我不能使用OR而不是AND,因为这将使我获得所有三个乐队,而不仅仅是一个鼓手和一个吉他手(所有吉他手将满足第一个子选择的条件)。

我不确定我理解你的目标是什么,但我也尝试过使用unions和values_list()的方法:

SELECT 
    `bands_band`.`id`, 
    `bands_band`.`name`, 
    COUNT(`bands_membership`.`id`) AS `num_mems` 
FROM `bands_band` INNER JOIN `bands_membership` ON ( `bands_band`.`id` = `bands_membership`.`band_id` ) 
WHERE (
    (`bands_membership`.`id`) IN (SELECT U0.`id` FROM `bands_membership` U0 WHERE U0.`instrument_id` = 1) 
AND (`bands_membership`.`id`) IN (SELECT U0.`id` FROM `bands_membership` U0 WHERE U0.`instrument_id` = 2)
)
GROUP BY `bands_band`.`id` HAVING COUNT(`bands_membership`.`id`) >= 2 ORDER BY NULL

memberships1 = Membership.objects.filter(instrument=guitar) memberships2 = Membership.objects.filter(instrument=drums) memberships = memberships1 | memberships2 memberships = memberships.values_list('id') bands = Band.objects.filter(reduce(operator.and_, (Q(memberships__in=x) for x in memberships))) bands = bands.annotate(num_mems=Count('memberships')).filter(num_mems__gte=len(memberships)) 再次返回0,这是查询:

bands.count()

我认为这个与上面基本相同。

由于我不确定我是否正确地读你,我也试过

SELECT 
    `bands_band`.`id`, 
    `bands_band`.`name`, 
    COUNT(`bands_membership`.`id`) AS `num_mems` 
FROM `bands_band` 
INNER JOIN `bands_membership` ON ( `bands_band`.`id` = `bands_membership`.`band_id` ) 
WHERE (
    `bands_membership`.`id` IN (1) AND `bands_membership`.`id` IN (2) AND `bands_membership`.`id` IN (3) 
AND `bands_membership`.`id` IN (4) AND `bands_membership`.`id` IN (5) AND `bands_membership`.`id` IN (6)
)
GROUP BY `bands_band`.`id` HAVING COUNT(`bands_membership`.`id`) >= 6 ORDER BY NULL

使用values_list方法。这得到了所有三个乐队,我认为这是预期的,但不是我想要的结果。

0 个答案:

没有答案