49

I have a stored procedure with a number of parameters. I would like to write my query so that it joins with certain tables but only if a particular parameter has a value. Take the following example: I have a Person table. There is also an Address table which holds Person Addresses and a Groups table that holds Person Groups. Both are one to many relationships with the Person table. My stored procedure has an @AddressID parameter and a @GroupID parameter.

The query always just returns fields from the Person table. If neither parameter has a value then the query should return all records from the Person table. If the @AddressID parameter is supplied then it should return only records that have a matching record in the Address table and ignore the Groups table. If the @GroupID parameter is supplied then it should return only records that have a matching record in the Groups table and ignore the Addresses table. If both parameters are supplied then it should only show records that have a matching record in both tables. Make sense?

Is there a simple way to do this that I am missing?

Thanks, Corey

1
  • 1
    Thanks for asking. Nobody else have. This is quite useful when using LEFT JOIN for example.
    – Jorge
    Commented Nov 1, 2016 at 17:58

9 Answers 9

50

If I understand correctly it sounds like your join conditions would be the equivalent of
ON ((@AddressID IS NOT NULL) AND (alias.column = @AddressID)) and likewise for the group join.

I use this conditional join at times.

7
  • Perfect! Worked like a charm. Thanks so much. Commented Dec 4, 2009 at 22:22
  • 20
    How is this correct? The inner join will return nothing if @AddressID is NULL
    – hidarikani
    Commented Aug 21, 2013 at 9:21
  • @hidarikani, Perhaps can try my solution here? stackoverflow.com/a/21694802/54908 Commented May 13, 2015 at 9:07
  • 1
    Not correct. Will return nothing if @AddressID is NULL. Commented Oct 11, 2016 at 21:45
  • 5
    @hidarikani You should use LEFT OUTER JOIN (and not INNER JOIN) to return values when @AddressID is NULL Commented May 19, 2018 at 12:23
33

The simple ways are actualy not good solutions. As bad as it sounds, the best solution is to have explicit IF in the code and separate queries:

IF (condition) 
  SELECT ... FROM Person WHERE ...
ELSE IF (otherCondition)
  SELECT ... FROM Person JOIN ... ON ... WHERE ...
ELSE IF (moreCondition)
  SELECT ... FROM Persons JOIN ... JOIN ... WHERE ...

The reason for this is that if you're trying to build one single query that matches all three (or more) conditions then the engine has to produce one single query plan that works in all conditions. In T-SQL one statement equals one plan. Remember that plans are created for the generic case, for any variable value, so the result is always a very, very bad plan.

While is is counterintuitive and seems like a horrible solution to any programmer, this is how databases work. The reason why this is not a problem 99.99% of the times is that after trying what you ask and seeing what it has to be done, developers quickly come to their senses and revise their requirements so that they never have to run queries that optionaly join based on runtime variable values ;)

4
  • 2
    LINQ2SQL is, perhaps surprisingly, a better fitr for something like this, because of the nature of query expressions being fully fledged objects that can be manipulated and refined, see stackoverflow.com/questions/1849005/… Commented Dec 4, 2009 at 22:31
  • 1
    That's fine if you have mutually exclusive criteria, which most search screens don't. And it increases the maintenance overhead - if a JOIN changes, you have to make the changes to all copies. Dynamic SQL is the more realistic solution in such situations.
    – OMG Ponies
    Commented Dec 5, 2009 at 2:57
  • @Pony: Yes, dynamic SQL is better for complex conditions. Commented Dec 5, 2009 at 6:25
  • Still, there are cases where having a single select is faster, like when you have an inline Table-valued function select (faster) versus a Multi-statement Table-valued function with an IF (slower).
    – CGodo
    Commented Aug 14, 2013 at 23:28
7

Yes, it's very simple. Do left joins on the address and groups. Then in the where clause...

(@group_id is null or g.group_id = @group_id)
and (@address_id is null or a.address_id = @address_id)
1
  • 2
    This will give the right answer, but the performance will be slow with OR conditions in you joins.
    – Eric Ness
    Commented Jan 24, 2014 at 14:07
3

You should be able to expand on this...

DECLARE @SQL varchar(max)

    SET @SQL = 'SELECT * FROM PERSON P'

    IF NULLIF(@ADDRESSID,"") IS NULL SET @SQL = @SQL + " INNER JOIN ADDRESSES A ON P.AddressID = A.AddressID"

    EXEC sp_executesql @SQL, N'@ADDRESSID int', @ADDRESSID
10
  • Why not? Dynamic SQL is great! Commented Dec 4, 2009 at 22:16
  • 8
    No, it's the devil's own spawn most of the time and should only be used in the absence of any real alternative. Simply trying to save a line of SQL does not count. Not only that, but your dynamic SQL will generate a less efficient query plan, complicates greatly any upgrades or auditing, and most importantly leaves you vulnerable to SQL injection attacks. Mostly it's for the lazy.
    – MartW
    Commented Dec 4, 2009 at 22:26
  • 4
    @Corey, Dynamic sql is not slow if you use sp_executesql and therefore utilize query plan caching. Commented Dec 4, 2009 at 22:44
  • 2
    Sigh, good luck injecting sql into that integer parameter, that i'd love to see.. Commented Dec 5, 2009 at 9:20
  • 1
    Most of the time it is devil's spawn. 999/1000 times it's used, it's used badly and dangerously. And the query plan does not only change if the parameters contain actual SQL - it will change if the nature of the parameters (such as above) affect the very nature of the SQL run. In a limited scenario, such as this one, it's unlikely to be a problem. The above may be safe against injection, but I still dislike it for interfering with auditing and complications with permissions. But I think what set me off was the phrase "Dynamic SQL is great!" - "Dynamic SQL is very powerful!" would be better
    – MartW
    Commented Dec 6, 2009 at 12:47
3

This is how I had done for my case.


DECLARE
    @ColorParam varchar(500)

SET
    @ColorParam = 'red, green, blue'

declare @Colors table
(
    Color NVARCHAR(50) PRIMARY KEY
)

-- populate @Colors table by parsing the input param, 
-- table can be empty if there is nothing to parse, i.e.: no condition
INSERT @Colors SELECT Value FROM dbo.Splitter(@ColorParam, ',')

SELECT
    m.Col1,
    c.Color
FROM
    MainTable AS m
FULL JOIN -- instead of using CROSS JOIN which won't work if @Colors is empty
    @Colors AS c
ON
    1 = 1 -- the trick
WHERE
    (@ColorParam IS NULL OR c.Color = m.Color)
    
1
  • Full join will return a bunch of nulls in the main table columns where the id's don't match up. U need a left join instead. Commented Oct 11, 2016 at 21:54
1

Uh, probably all of you have resolved this so far.

As i understand You, you want to have 'dynamic' query, to join table if parameter exists, or to omit join if parameter is null. Secret is in using left outer join. Like:

SELECT p.*
FROM Parent AS p
LEFT OUTER JOIN Child AS c ON p.Id = c.ParentId
WHERE
        (@ConditionId IS NULL OR c.ConditionId = @ConditionId)

How this works?

  • If filter parameter @ConditionId is null, then there's no child for outer join, and result will have all Parent's.
  • If filter parameter @ConditionId is not null, then outer join will join Child's with this parent, and condition (@ConditionId IS NULL OR c.ConditionId = @ConditionId) will throw out Parent's which didn't have joined Child's with condition c.ConditionId = @ConditionId.

LEFT OUTER JOIN for sure have performance issue, but as much as this works fast, i don't want to concatenate string's to create query.

0

What Quntin posted is nice however there are some performance issues with it. Believe it or not what is faster is to check each parameter and write the SQL Join based on the case

In addition:

IF @AddressParameter IS NOT NULL
BEGIN
SELECT blah1, blah2 FROM OneTable INNER JOIN AddressTable WHERE ....
-more code
END
ELSE...
BEGIN
END
...

Another thing you can do is perform the joins and in the query filter (the where clause) you can do:

WHERE
(Address = @Address OR @Address IS NULL)

Performance here is shady as well.

3
  • Well that won't really work in this situation. I have about 9 parameters that may or may not be null. The possible combinations of all of those different parameters would be like 9 factorial or some huge number. Commented Dec 4, 2009 at 22:17
  • Im not sure I understand what you mean Corey. All you would do is handle that in the WHERE for n Number of parameters. In addition, WHERE ( (Address = @Address OR @Address IS NULL) AND (Group = @Group OR @Group IS NULL) AND (...))
    – JonH
    Commented Dec 4, 2009 at 22:27
  • A simple join will exclude records. It need to be a left join. Commented Oct 11, 2016 at 21:44
0

Join the three tables together and use something like this in your WHERE clause:

WHERE Addresses.ID = COALESCE(@AddressID, Addresses.ID)
AND  Groups.ID = COALESCE(@GroupID, Groups.ID)
1
  • A simple join will exclude records. It need to be a left join i believe. Commented Oct 11, 2016 at 22:02
0

Left join and where clause should do the trick:

SELECT Customers.CustomerName, Customers.Country, Orders.OrderID
    FROM Customers
    LEFT JOIN Orders
    ON Customers.CustomerID=Orders.CustomerID
    WHERE Country= @MyOptionalCountryArg or @MyOptionalCountryArg is null;

Not the answer you're looking for? Browse other questions tagged or ask your own question.