Reputation: 389
Given this user and User's items table:
With this select below I'm able to get the candidates that have both item 3 and 4:
Select
U.Id,
U.[Name],
STRING_AGG(UI.ItemId, ',') as Items
from Users U
Left Join UserItems UI on UI.UserId = U.Id
Where (
UI.ItemId IN (3, 4)
)
Group By U.Id, U.[Name]
Having Count(*) = 2
But how can I get all candidates using more conditions? For example:
UI.ItemId IN (2, 7) OR UI.ItemId IN (3,4) OR UI.ItemId = 2
Any idea on how to solve this?
Upvotes: 0
Views: 2920
Reputation: 1
If you want to get all users with their ItemIds try this:
Select
U.Id,
U.[Name],
STRING_AGG(UI.ItemId, ',') as Items
from Users U
left JOIN UserItems UI on UI.UserId = U.Id
Group By U.Id, U.[Name]
You should remove 'where clause' if you want all users. Do not use 'Having clause' in this case
Upvotes: 0
Reputation: 1391
DECLARE @ids TABLE (id INT)
INSERT INTO @ids
VALUES (1), (3)
Select
U.Id,
U.[Name],
STRING_AGG(UI.ItemId, ',') as Items
from Users U
Left Join UserItems UI on UI.UserId = U.Id
INNER JOIN @ids I on UI.ItemId = I.id
Group By U.Id, U.[Name]
HAVING(COUNT(*) = (SELECT COUNT(*) FROM @ids))
Upvotes: 0
Reputation: 42632
SELECT ...
FROM ...
GROUP BY U.Id, U.[Name]
HAVING 2 = COUNT(DISTINCT CASE WHEN UI.ItemId IN (3, 4) THEN UI.ItemId END)
AND 3 = COUNT(DISTINCT CASE WHEN UI.ItemId IN (5, 6, 7) THEN UI.ItemId END)
AND ...
UI.ItemId IN (2, 7) OR UI.ItemId IN (3,4) OR UI.ItemId = 2
HAVING 2 = COUNT(DISTINCT CASE WHEN UI.ItemId IN (2, 7) THEN UI.ItemId END)
OR 2 = COUNT(DISTINCT CASE WHEN UI.ItemId IN (3, 4) THEN UI.ItemId END)
OR 1 = COUNT(DISTINCT CASE WHEN UI.ItemId IN (2) THEN UI.ItemId END)
If the condition means "one of 3 listed conditions, and no records out of matched condition", then modify each separate into
HAVING ( 2 = COUNT(DISTINCT CASE WHEN UI.ItemId IN (2, 7) THEN UI.ItemId END)
AND 0 = COUNT(DISTINCT CASE WHEN UI.ItemId NOT IN (2, 7) THEN UI.ItemId END)
)
OR ...
Upvotes: 4
Reputation: 1269743
First, you are looking for matches between the two tables. Use a JOIN
rather than LEFT JOIN
.
You can use a more complex having
clause:
having (sum(case when ui.itemid = 2 then 1 else 0 end) > 0 and
sum(case when ui.itemid = 7 then 1 else 0 end) > 0
) or
(sum(case when ui.itemid = 3 then 1 else 0 end) > 0 and
sum(case when ui.itemid = 4 then 1 else 0 end) > 0
) or
sum(case when ui.itemid = 2 then 1 else 0 end) > 0
Of course, the first condition is a subset of the last condition, so it is not necessary. But this illustrates the basic idea.
If you know you have no duplicates -- as your original query suggests -- then I would also suggest:
having sum(case when ui.itemid in (2, 7) then 1 else 0 end) = 2 or
sum(case when ui.itemid in (3, 4) then 1 else 0 end) = 2 or
sum(case when ui.itemid = 2 then 1 else 0 end) = 1
Upvotes: 2
Reputation: 222452
You can use aggregation as follows:
select
u.id,
u.name,
string_agg(i.itemId, ',') items
from users u
left join userItems i on i.userId = u.id
group by u.id, u.name
having
max(case when i.itemId = 3 then 1 end) = 1
and max(case when i.itemId = 4 then 1 end) = 1
You can easily expand the having
clause with more combinations, like:
having
-- users that have both items 3 and 4
(
max(case when i.itemId = 3 then 1 end) = 1
and max(case when i.itemId = 4 then 1 end) = 1
)
-- users that have both items 2 and 7
or (
max(case when i.itemId = 2 then 1 end) = 1
and max(case when i.itemId = 7 then 1 end) = 1
)
-- users that have items 1
or max(case when i.itemId = 1 then 1 end) = 1
Upvotes: 0