Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find records with exact matches on a many to many relationship

I have three tables that look like these:

PROD

Prod_ID|Desc
------------
P1|Foo1
P2|Foo2
P3|Foo3
P4|Foo4
...

RAM

Ram_ID|Desc
------------
R1|Bar1
R2|Bar2
R3|Bar3
R4|Bar4
...

PROD_RAM

Prod_ID|Ram_ID
------------
P1|R1
P2|R2
P3|R1
P3|R2
P3|R3
P4|R3
P5|R1
P5|R2
...

Between PROD and RAM there's a Many-To-Many relationship described by the PROD_RAM table.

Given a Ram_ID set like (R1,R3) I would like to find all the PROD that has exactly ONE or ALL of the RAM of the given set.

Given (R1,R3) should return for example P1,P4 and P5; P3 should not be returned because has R1 and R3 but also R2.

What's the fastest query to get all the PROD that has exactly ONE or ALL of the Ram_ID of a given RAM set?

EDIT:
The PROD_RAM table could contain relationship bigger than 1->3 so, "hardcoded" checks for count = 1 OR = 2 are not a viable solution.

like image 938
systempuntoout Avatar asked Jun 14 '11 07:06

systempuntoout


People also ask

How do you show many-to-many relationships in a database?

When you need to establish a many-to-many relationship between two or more tables, the simplest way is to use a Junction Table. A Junction table in a database, also referred to as a Bridge table or Associative Table, bridges the tables together by referencing the primary keys of each data table.

How do you solve a many-to-many relationship in SQL?

Many-to-many (M:M) A relationship is many-to-many if and only if one record from table A is related to one or more records in table B and vice-versa. To establish a many-to-many relationship, create a third table called "ClassStudentRelation" which will have the primary keys of both table A and table B.

How can I get matching records from two tables?

(INNER) JOIN : Returns records that have matching values in both tables. LEFT (OUTER) JOIN : Returns all records from the left table, and the matched records from the right table. RIGHT (OUTER) JOIN : Returns all records from the right table, and the matched records from the left table.

How do you use a many-to-many relationship?

A many-to-many relationship occurs when multiple records in a table are associated with multiple records in another table. For example, a many-to-many relationship exists between customers and products: customers can purchase various products, and products can be purchased by many customers.


3 Answers

Another solution you could try for speed would be like this

;WITH CANDIDATES AS (
    SELECT  pr1.Prod_ID
            , pr2.Ram_ID
    FROM    PROD_RAM pr1
            INNER JOIN PROD_RAM pr2 ON pr2.Prod_ID = pr1.Prod_ID
    WHERE   pr1.Ram_ID IN ('R1', 'R3')
)
SELECT  *
FROM    CANDIDATES
WHERE   CANDIDATES.Prod_ID NOT IN (
            SELECT  Prod_ID
            FROM    CANDIDATES
            WHERE   Ram_ID NOT IN ('R1', 'R3')
        )           

or if you don't like repeating the set conditions

;WITH SUBSET (Ram_ID) AS (
    SELECT  'R1'
    UNION ALL SELECT 'R3'
)   
, CANDIDATES AS (
    SELECT  pr1.Prod_ID
            , pr2.Ram_ID
    FROM    PROD_RAM pr1
            INNER JOIN PROD_RAM pr2 ON pr2.Prod_ID = pr1.Prod_ID
            INNER JOIN SUBSET s ON s.Ram_ID = pr1.Ram_ID    
)
, EXCLUDES AS (
    SELECT  Prod_ID
    FROM    CANDIDATES
            LEFT OUTER JOIN SUBSET s ON s.Ram_ID = CANDIDATES.Ram_ID
    WHERE   s.Ram_ID IS NULL
)
SELECT  *
FROM    CANDIDATES
        LEFT OUTER JOIN EXCLUDES ON EXCLUDES.Prod_ID = CANDIDATES.Prod_ID
WHERE   EXCLUDES.Prod_ID IS NULL        
like image 56
Lieven Keersmaekers Avatar answered Oct 25 '22 09:10

Lieven Keersmaekers


One way to do this would be something like the following:

SELECT PROD.Prod_ID FROM PROD WHERE

(SELECT COUNT(*) FROM PROD_RAM WHERE PROD_RAM.Prod_ID = PROD.Prod_ID) > 0 AND

(SELECT COUNT(*) FROM PROD_RAM WHERE PROD_RAM.Prod_ID = PROD.Prod_ID AND PROD.Ram_ID <> 
IFNULL((SELECT TOP 1 Ram_ID FROM PROD_RAM WHERE PROD_RAM.Prod_ID = PROD.Prod_ID),0)) = 0
like image 1
cusimar9 Avatar answered Oct 25 '22 09:10

cusimar9


SELECT Prod_ID
FROM 
  ( SELECT Prod_ID
         , COUNT(*) AS cntAll
         , COUNT( CASE WHEN Ram_ID IN (1,3)
                       THEN 1
                       ELSE NULL
                  END 
                ) AS cntGood
    FROM PROD_RAM
    GROUP BY Prod_ID
  ) AS grp
WHERE cntAll = cntGood
  AND ( cntGood = 1
     OR cntGood = 2               --- number of items in list (1,3)
      )

Not at all sure if it's the fastest way. You'll have to try different ways to write this query (using JOINs and NOT EXISTS ) and test for speed.

like image 1
ypercubeᵀᴹ Avatar answered Oct 25 '22 09:10

ypercubeᵀᴹ