Home > Mobile >  Change SQL where clause based on a parameter?
Change SQL where clause based on a parameter?

Time:11-23

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, and ProductTag contains the joins between them and Product. If there is no such table, and Tags is just a list of tags per Product, then you can replace Tags with (SELECT DISTINCT TagId FROM Tags) and ProductTag with Tags.

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
);
  • Related