tags:

views:

547

answers:

6

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

+2  A: 

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.

Quintin Robinson
Perfect! Worked like a charm. Thanks so much.
Corey Burnett
+2  A: 

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)
dotjoe
+1  A: 

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
Paul Creasey
@Paul - Don't do this
JonH
Why not? Dynamic SQL is great!
Paul Creasey
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.
CodeByMoonlight
Yeah - I am trying to avoid dynamic SQL on this one. I am using it currently and it is very, very slow.
Corey Burnett
@CodeByMoonlight, I disagree strongly. It's not always the right choice but taking this problem as an example, it is the best approach imo. The idea that the quey plan is less efficient is nonsense, which method would you advocate to solve this problem?
Paul Creasey
@Corey, Dynamic sql is not slow if you use sp_executesql and therefore utilize query plan caching.
Paul Creasey
I've used the accepted solution or JonH's a few times, but also ditched them on other occasions when performance suffers. I'd lean towards Remus' option when possible. As for sp_executesql, it's not a panacaea. You won't get the benefit a lot of the time if the variables passed cause the underlying query to change radically.
CodeByMoonlight
The only way that would happen is if you are using parameters which contain actual SQL, i agree that is almost never a good plan. Remus' solution would perform near identically to mine, and i'd argue mine is far more maintainable and auditable, especially with many options. Dynamic SQL needs to be treated with caution, but it is very far from being devil's spawn! :)
Paul Creasey
Paul it doesn't fare well for other reasons, including readibility, the use of exec, and it always reaks of sql injection.
JonH
Sigh, good luck injecting sql into that integer parameter, that i'd love to see..
Paul Creasey
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
CodeByMoonlight
A: 

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.

JonH
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.
Corey Burnett
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
+4  A: 

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

Remus Rusanu
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 http://stackoverflow.com/questions/1849005/converting-conditionally-built-sql-where-clause-into-linq/1849041#1849041
Remus Rusanu
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
@Pony: Yes, dynamic SQL is better for complex conditions.
Remus Rusanu
A: 

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)
JML