I need to alter a query to do something like this (following is generic pseudo-code):
if (tag list contains all tags in the database) {
select every product regardless of tag, even products with null tag
}
else { //tag list is only a few tags long
select only the products that have a tag in the tag list
}
I have tried doing stuff like this, but it doesn't work:
SELECT p.Id
FROM Tags t
JOIN Products p ON p.TagId = t.Id
WHERE ((EXISTS(select Id from Tags EXCEPT select item from dbo.SplitString(@tagList,',')) AND p.TagId in (select item from dbo.SplitString(@tagList,',')))
OR (p.TagId in (select item from dbo.SplitString(@tagList,',')) or p.TagId is null))
This will take place inside of a large query with a large WHERE clause, so putting two slightly different queries in an IF ELSE statement is not ideal.
What should I do to get this working?
CodePudding user response:
Try this, this might work.
SELECT p.Id
FROM
Products p LEFT JOIN
Tags t ON p.TagId = t.Id
WHERE
t.Id is null
OR
(t.id is not null and
t.Id in (SELECT value FROM STRING_SPLIT(@tagList, ',')))
I just tested - works
CodePudding user response:
First things first: you should use properly normalized input parameters. Ideally this would be a Table-Valued parameter, however if you cannot do that then you could insert the split values into a table variable
DECLARE @tags TABLE (TagId int PRIMARY KEY);
INSERT @tags (TagId)
SELECT item
FROM dbo.SplitString(@tagList, ',');
Next, the easiest way is probably to just find out first whether all tags match, and store that in a variable.
I'm assuming that
Tags
actually contains a list of all possible tags, andProductTag
contains the joins between them andProduct
. If there is no such table, andTags
is just a list of tags perProduct
, then you can replaceTags
with(SELECT DISTINCT TagId FROM Tags)
andProductTag
withTags
.
DECLARE @isAllTags bit = CASE WHEN EXISTS(
SELECT t.Id
FROM Tags t
EXCEPT
SELECT tList.Id
FROM @tags tList
) THEN 0 ELSE 1 END;
SELECT p.Id
FROM Products p
WHERE @isAllTags = 1
OR EXISTS (SELECT 1
FROM ProductTag pt
JOIN @tags tList ON tList.TagId = pt.TagId);
You could merge these queries, but it's unlikely to be more performant.
You could even do it in a very set-based fashion, but it's probably going to be really slow
SELECT p.Id
FROM Products p
WHERE EXISTS (SELECT 1
FROM Tags t
LEFT JOIN @tags tList ON tList.TagId = t.Id
LEFT JOIN ProductTag pt ON pt.TagId = tList.TagId
HAVING COUNT(t.Id) = COUNT(tList.TagId) -- all exist
OR COUNT(pt.TagId ) > 0 -- at least one match
);