Django: Retrieve list of unique m2m combinations

Question:

I have the following model schema in Django (Note that I left out a detailed overview of class ModelB as it is irrelevant for the question)

class ModelA(Models.model):
    b = models.ManyToManyField(ModelB, blank=True)

Given a QuerySet of ModelA objects, I would like to receive all unique M2M combinations (of ModelB objects) that are present in the queryset. For example, for the following queryset of 4 ModelA objects:

| id       | b (m2m field)    | 
| -------- | ---------------- | 
| 1        | [1,2,3]          |
| 2        | [1,2]            |
| 3        | [1,2]            |
| 4        | []               |

I would like to return a list/queryset that looks like [[1,2,3],[1,2],[]]. Is there an elegant way to do this in Django? I have tried to do the following: `

queryset.values_list('b',flat=True).distinct() 

But this returns a flat list of all unique ModelB objects present in the entire table (e.g.: [1,2,3]). Hence, I lose the granularity of the combinations in which the ModelB objets occur in the m2m field.

Asked By: Wout_Gooss

||

Answers:

You can access the through model via ModelA.b.through. From there you can query on that model which will have fields to both ModelB and ModelA. The model structure would be something similar to:

ThroughModel:
    id: int
    a_id: int
    a: ModelA
    b_id: int
    b: ModelB

From there you’d still need to devise the query to fetch the data in the shape that you want. You should be able to achieve it with Subquery and ArrayAgg

Answered By: schillingt

For future readers with the same question, I ended up using a combination of annotate and and ArrayAgg like this:

unique_combinations = (
    ModelA.objects.annotate(id_list=ArrayAgg("b", ordering="b", distinct=True))
    .values("id_list")
    .distinct()
)

I first annotate each object with the id_list field, which contains all the sorted and unique ids of the ModelB objects in the m2m relation. I then retrieve the id_list values from the queryset and only keep the distinct items. The result is a queryset containing all (unique) m2m combinations of ModelB objects present in the ModelA queryset.

Note that it is important to add the ordering when applying ArrayAgg during annotation, as the .distinct() operator does not filter out combinations with the same entries but with different ordering.

Answered By: Wout_Gooss
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.