10

This is closely related to this question, but adds another requirement.

Given a parent table 'parent'

╔════════════╦════════╗
║ PARENT_ID  ║ NAME   ║
╠════════════╬════════╣
║         1  ║ bob    ║
║         2  ║ carol  ║
║         3  ║ stew   ║
╚════════════╩════════╝

and a many-many relationship table 'rel' between parent and a (here unspecified) property table

╔════════════╦══════════╗
║ PARENT_ID  ║ PROP_ID  ║
╠════════════╬══════════╣
║         1  ║       5  ║
║         1  ║       1  ║
║         2  ║       5  ║
║         2  ║       4  ║
║         2  ║       1  ║
║         3  ║       1  ║
║         3  ║       3  ║
╚════════════╩══════════╝

How can I select all parents that have all of and only a specified set of relationships? E.g. with the sample data, how can I find all parents that have relationships with exactly property 5 and 1?

Community
  • 1
  • 1
Laizer
  • 5,932
  • 7
  • 46
  • 73

10 Answers10

14
SELECT PARENT_ID
FROM rel
GROUP BY PARENT_ID
HAVING SUM(PROP_ID NOT IN (5,1)) = 0
   AND SUM(PROP_ID = 1) = 1 
   AND SUM(PROP_ID = 5) = 1
juergen d
  • 201,996
  • 37
  • 293
  • 362
4

This alternative has the benefit of a constant statement structure and only one parameter, independent of the amount of relations you are looking for:

SELECT parent_id FROM rel 
GROUP BY parent_id 
HAVING GROUP_CONCAT(prop_id ORDER BY prop_id ASC SEPARATOR ",") = '1,5';

Disadvantages:

  • You need to prepare an ordered, comma separated String of prop_ids upfront.
  • This works on MySQL, but not all database servers.
3

If you want to select all parents with at least a 5 and a 1, you can use:

SELECT PARENT_ID
FROM rel
GROUP BY PARENT_ID
HAVING SUM(PROP_ID = 1)
       AND SUM(PROP_ID = 5)
       AND SUM(PROP_ID NOT IN (5,1)) = 0

If you need exactly one 5 and one 1, see this answer

Community
  • 1
  • 1
Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
3
SELECT PARENT_ID
FROM rel
GROUP BY PARENT_ID
HAVING
  COUNT(PROP_ID)=2 AND
  COUNT(DISTINCT case when PROP_ID IN ( 1, 5 ) then PROP_ID end)=2

This will select all PARENT_ID that have exactly two rows, with exactly two, non duplicated, PROP_ID that match.

fthiella
  • 48,073
  • 15
  • 90
  • 106
2

With two nested subqueries, like this..

 Select pa.Id
 From parents pa
 Where not exists -- This ensures that all specifies properties exist
    (Select * From property y
     Where propertyId In (1,5)
         And Not Exists
             (Select * From parentProperty
              Where parentId = pa.parentId 
                  And propertyId = y.propertyId ))
   And not exists -- This ensures that only specified list of properties exist
    (Select * From parentProperty
     Where parentId = pa.parentId 
        And propertyId Not In (1,5) )

The first one reads "Show me all the parents where there is not a property in the specified list of properties that is not in the parent properties table for the specified parent...."

The second subquery reads: "also make sure that there does not exist a record in the parentProperties table for that parent for any property that is not in the specified list."

Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
1

Assuming (PARENT_ID, PROP_ID) is unique:

SELECT r1.PARENT_ID
FROM rel r1
INNER JOIN rel r2 ON r1.PARENT_ID = r2.PARENT_ID AND r2.PROP_ID = 5 
INNER JOIN rel r3 ON r1.PARENT_ID = r3.PARENT_ID AND r3.PROP_ID = 1
GROUP BY r1.PARENT_ID
HAVING COUNT(*) = 2

Or,

SELECT parent.PARENT_ID
FROM parent
INNER JOIN
(
    SELECT PARENT_ID
    FROM rel
    WHERE PROP_ID IN (1,5)
    GROUP BY PARENT_ID
    HAVING COUNT(*) = 2
) good ON parent.PARENT_ID = good.PARENT_ID
LEFT OUTER JOIN rel bad ON parent.PARENT_ID = bad.PARENT_ID 
    AND bad.PROP_ID NOT IN (1,5)
WHERE bad.PARENT_ID IS NULL

Or even,

SELECT DISTINCT parent.PARENT_ID
FROM parent
INNER JOIN rel r2 ON parent.PARENT_ID = r2.PARENT_ID AND r2.PROP_ID = 5 
INNER JOIN rel r3 ON parent.PARENT_ID = r3.PARENT_ID AND r3.PROP_ID = 1
LEFT OUTER JOIN rel r0 ON parent.PARENT_ID = r0.PARENT_ID 
    AND r0.PROP_ID NOT IN (1,5)
WHERE r0.PARENT_ID IS NULL
lc.
  • 113,939
  • 20
  • 158
  • 187
0

If MySql supported minus, the query could look like this:

select parent_id
from rel
where prop_id in (5,1)
group by parent_id
having count(distinct prop_id)=2 and count(prop_id)=2
minus
select parent_id
from rel
where prop_id not in (5,1);

The not in will remove those relationships that exceed (5,1), e.g. (5,1,3).

I know you're using MySql and my answer hence is wrong. Just take it as another idea.

sorencito
  • 2,517
  • 20
  • 21
  • MySql doesn't support minus, but even if it would, your query will return also parent_id where just one of the attributes matches, and also parent_id where duplicated attributes exists. pls see this fiddle http://sqlfiddle.com/#!4/ed577/6/0 second query should be correct – fthiella Jan 01 '13 at 21:38
  • @fthiella Thanks for your input - I corrected the query according to your fiddle. I didn't expect the table to contain duplicates and did not notice the mistake in the first IN clause. – sorencito Jan 02 '13 at 08:17
0

This query is true even if (PARENT_ID, PROP_ID) is not unique:

SELECT PARENT_ID FROM rel WHERE
PROP_ID IN (5,1) AND 
PARENT_ID NOT IN (SELECT DISTINCT PARENT_ID FROM rel WHERE PROP_ID NOT IN (5,1))
GROUP BY PARENT_ID HAVING COUNT(DISTINCT PROP_ID) = 2
Hajjari
  • 1
  • 1
-1

Hope this will help you:

SELECT p.PARENT_ID , r.PROP_ID FROM rel r LEFT JOIN parent p ON p.PARENT_ID = r.PARENT_ID WHERE r.PROP_ID = 5 OR r.PROP_ID = 1
gezimi005
  • 381
  • 1
  • 8
  • The output is wrong, as it contains duplicates and also those parent_ids with other relationships than (5,1). – sorencito Jan 01 '13 at 21:02
-3

You can do this with a group by.

SELECT PARENT_ID FROM link_tbl WHERE PROP_ID IN (5,1) GROUP BY PARENT_ID HAVING COUNT(*) = 2

user836087
  • 2,271
  • 8
  • 23
  • 33