Search code examples
sqldjangodjango-mptt

SQL query on Many to Many with intersection


I am working with django-categories which uses django-mptt under the hood. (That implicates db structure that I am using.)

I have some products:

product       
--------      
id | name     
1  | Apple    
2  | Orange   
3  | Tomato   

Categories:

categories
----------
id | name       | parent | tree_id | level
1  | Type       | null   | 1       | 1
2  | Fruit      | 1      | 1       | 2
3  | Vegetable  | 1      | 1       | 2
4  | Color      | null   | 2       | 1
5  | Orange     | 4      | 2       | 2
6  | Red        | 4      | 2       | 2
7  | Green      | 4      | 2       | 2
8  | Dark green | 7      | 2       | 3
9  | Orange     | 4      | 2       | 2

Which is tree with single root (nodes have also fields order, left, right but I don't think that it is relevant here):

root/
├── Type/
│   ├── Fruit
│   ├── Vegetable
└── Color/
    ├── Red
    ├── Green
    |   └── Dark green
    └── Orange

And M2M table:

product_categories
------------------
id | product_id | category_id
1  | 1          | 2
2  | 1          | 6
3  | 2          | 2
4  | 2          | 9
5  | 3          | 3
6  | 3          | 5

So I am having all categories in one tree, but I can group them by tree_id. Now I would like to find products that are Fruits and are Red OR Orange (so to get Apple an Orange)

I was expecting that query to give me what I want:

SELECT DISTINCT 
"product"."id", "product"."name" 
FROM
"product" 
INNER JOIN "product_categories" ON ("product"."id" = "product_categories"."product_id") 
WHERE 
("product_categories"."category_id" IN ('2') 
AND "product_categories"."category_id" IN ('6', '9'))

but I am getting no results.

It could be achieved by querying multiple times, once per tree, and then looking for intersection, but I believe it is doable by single query.


Solution

  • Try this:

    SELECT DISTINCT 
    "product"."id", "product"."name" 
    FROM
    "product" 
    INNER JOIN "product_categories" ON ("product"."id" = "product_categories"."product_id") 
    WHERE 
    ("product_categories"."category_id" IN ('6', '9'))
    INTERSECT
    SELECT DISTINCT 
    "product"."id", "product"."name" 
    FROM
    "product" 
    INNER JOIN "product_categories" ON ("product"."id" = "product_categories"."product_id") 
    WHERE 
    ("product_categories"."category_id" IN ('2'))