Reputation: 165
I am using SQL Server 2014 database, and SQL Server Management Studio to create and run queries.
Tables are :
Persons
| ID | personName |
+----+------------+
| 1 | Hamish |
| 2 | Morag |
| 3 | Ewan |
Cars
| ID | CarName |
+----+---------+
| 1 | Humber |
| 2 | Austen |
| 3 | Morris |
Gadgets
| ID | GadgetName |
+----+------------+
| 1 | Cassette |
| 2 | CD |
| 3 | Radio |
CarToPersonMap
| ID | CarID | PersonID |
+----+-------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 3 | 2 |
CarToGadgetMap
| ID | CarID | GadgetID |
+----+-------+----------+
| 1 | 2 | 2 |
The map tables have the appropriate foreign keys.
I want to find people who have a Cars but none of the Cars have gadgets. So in the example above I want to find Morag who has a Car with no Gadgets. Hamish has 2 Cars but 1 one the Cars has a gadget so I do not want the ResultSet to include Hamish.
Upvotes: 0
Views: 711
Reputation: 72415
You can do conditional aggregation inside an EXISTS
SELECT personName
FROM Persons p
WHERE EXISTS (SELECT 1
FROM CarToPersonMap cpm
LEFT JOIN CarToGadgetMap cgm ON cpm.carId = cgm.carID
WHERE cpm.personId = p.ID
HAVING COUNT(cgm.carID) = 0
);
What this says is:
Persons
that have rows in the subquery.CarToPersonMap
, left joined with relevant CarToGadgetMap
.CarToGadgetMap
value (because COUNT
only counts non-nulls).Upvotes: 0
Reputation: 1134
You can use EXISTS
also to get the expected output. The query will be something like,
SELECT DISTINCT personName
FROM Persons p
WHERE
EXISTS ( SELECT 1 FROM CarToPersonMap WHERE personId = p.ID )
AND NOT EXISTS ( SELECT 1
FROM CarToGadgetMap cgm
JOIN CarToPersonMap cpm ON cpm.personId = p.ID AND cpm.carId = cgm.carId)
here is the fiddle
Upvotes: 0
Reputation: 1057
I'd use a couple of CTEs:
With CarGadgetCount AS (
SELECT c.ID as CarID,
SUM(CASE cg.ID IS NOT NULL THEN 1 ELSE 0 END) AS GadgetCount
FROM Car c
LEFT JOIN CarToGadgetMap cg
ON cg.CarID = c.ID
GROUP BY c.ID
),
PersonCarCount AS (
SELECT p.ID as PersonID,
SUM(CASE cp.ID IS NOT NULL THEN 1 ELSE 0 END) AS CarCount
FROM Person p
LEFT JOIN CarToPersonMap cp
ON cp.PersonID = p.ID
)
PersonGadgetCount AS (
SELECT p.ID AS PersonID,
SUM(CASE WHEN cg.GadgetCount IS NULL THEN 0 ELSE cg.GadgetCount END) as GadgetCount
FROM Person p
LEFT JOIN CarToPersonMap cp
ON cp.PersonID = p.ID
LEFT JOIN CarGadgetCount cg
ON cg.CarID = cp.CarID
GROUP BY p.ID
)
SELECT p.personName
FROM Person p
INNER JOIN PersonGadgetCount pg
ON pg.PersonID = p.ID
INNER JOIN PersonCarCount pc
ON pc.PersonID = p.ID
WHERE pc.CarCount > 0
AND pg.GadgetCount = 0;
That way, if you want to actually see who has cars and who has gadgets, then you can switch the WHERE clause and specified columns in the select
Upvotes: 0
Reputation: 29677
Left join the CarToGadgetMap to the CarToPersonMap.
Then group by the person.
Those persons will be having more than 0 cars but 0 gadgets.
select PersonName from Persons as Person left join CarToPersonMap as CarPerson on CarPerson.PersonId = Person.ID left join CarToGadgetMap as CarGadget on CarGadget.carId = CarPerson.CarId group by Person.ID, PersonName having count(CarPerson.CarID) > 0 -- has car(s) and count(CarGadget.GadgetID) = 0 -- no gadgets
PersonName |
---|
Morag |
Demo on db<>fiddle here
Upvotes: 2