group-bymariadbmany-to-manyhavingintersect

MariaDB - Conjunction-Search in Many-to-Many


I have problems to implement an "and-concatenated" search with many-to-many tables. I tried to present a simple example below. I use MariaDB. I have a table with process. To the process a can assign persons and tags. There is a table for tags and a table for persons. There a two many-to-many relationships: tags_to_processes and persons_to_processes.

  1. example: Find all process with person 1 and person 2 and with tag 1 and 2. Result: process 1.

  2. example: Find all process with person 1 and person 2 and with tag 2. Result: Process 1 and Process 2.

Thank you very much!

'processes' Table
+-----------+-------------------+
|process_id |process_name       |
+-----------+-------------------+
|1          |Process 1          |
|2          |Process 2          |
|3          |Process 3          |
+-----------+-------------------+

'persons' table
+----------+------------+
|person_id |person_name |
+----------+------------+
|1         |Person 1    |
|2         |Person 2    |
|3         |Person 3    |
|4         |Person 4    |  
|5         |Person 5    |
+----------+------------+

'tags' table
+----------+-----------+
|tag_id    |tag_name   |
+----------+-----------+
|1         |Tag 1      |
|2         |Tag 2      |
|3         |Tag 3      |
|4         |Tag 4      |
|5         |Tag 5      |
|6         |Tag 6      |
+----------+-----------+

'persons_to_processes' table
+----------+-----------+
|person_id |process_id |
+----------+-----------+
|1         |1          |
|2         |1          |
|3         |1          |
|4         |1          |
|5         |1          |
|1         |2          |
|2         |2          |
|4         |3          |
+----------+-----------+

'tags_to_processes' table
+----------+-----------+
|tag_id    |process_id |
+----------+-----------+
|1         |1          |
|2         |1          |
|3         |1          |
|6         |1          |
|2         |2          |
|2         |3          |
+----------+-----------+

Solution

  • You can join persons_to_processes to persons, filter the resuults for the persons that you want and use aggregation:

    SELECT ptp.process_id
    FROM persons_to_processes ptp INNER JOIN persons p
    ON p.person_id = ptp.person_id
    WHERE p.person_name IN ('Person 1', 'Person 2')
    GROUP BY ptp.process_id
    HAVING COUNT(*) = 2 -- 2 persons
    

    Similarly for the tables tags_to_processes and tags:

    SELECT ttp.process_id
    FROM tags_to_processes ttp INNER JOIN tags t
    ON t.tag_id = ttp.tag_id
    WHERE t.tag_name IN ('Tag 1', 'Tag 2')
    GROUP BY ttp.process_id
    HAVING COUNT(*) = 2 -- 2 tags
    

    Finally, you can combine the 2 queries to get their common results with INTERSECT:

     WITH 
      cte1 AS (
        SELECT ptp.process_id
        FROM persons_to_processes ptp INNER JOIN persons p
        ON p.person_id = ptp.person_id
        WHERE p.person_name IN ('Person 1', 'Person 2')
        GROUP BY ptp.process_id
        HAVING COUNT(*) = 2 -- 2 persons  
      ),
      cte2 AS (
        SELECT ttp.process_id
        FROM tags_to_processes ttp INNER JOIN tags t
        ON t.tag_id = ttp.tag_id
        WHERE t.tag_name IN ('Tag 1', 'Tag 2')
        GROUP BY ttp.process_id
        HAVING COUNT(*) = 2 -- 2 tags  
      )
    SELECT process_id FROM cte1
    INTERSECT
    SELECT process_id FROM cte2;
    

    See the demo.