Alternative to NULL check and then OR for optional paramaters - sql

I use this pattern for optional filter paramaters in my SQL Stored Procedures
AND (
#OptionalParam IS NULL OR
(
Id = #OptionalParam
)
)
However the OR is not a friend of the query optimizer. Is there a more efficient way to do this without using dynamic SQL

You can try using COALESCE. Not sure if it will be more efficient.
AND Id = Coalesce(#OptionalParam, Id)
This will not work if Id itself is null and you are using ANSI nulls.

AND ID = ISNULL(#OptionalParam, ID)
or you if you had multiple optional parameters can use
AND ID = COALESCE(#OptionalParam1, #OptionalParam2, ID)
This is definitely faster than using an OR statement.
Like the other answerer mentioned, this will not work if the ID column is null (but then again, the original statement wouldn't either).

You could try:
AND Id = CASE WHEN #OptionalParam IS NULL THEN Id ELSE NULL END
I doubt this will optimize much better, but there's no OR in it.
Alternatively, you could break your query apart into two components -- one with just an #OptionalParam IS NULL test and another with an Id = #OptionalParam test, then UNION them together. Depending on your data topology this might yield better results. It could also be significantly worse.

Related

SQL: I need to return all record when 'showall' is found in a parameter but when not found it needs to filter from that parameter

#Network = 'showall,A,B,C' or #Network = 'A,B,C'
When 'showall' is present i need to show all records but if not found i need to filter records to what is in the list.
WHERE rolename in ('A','B','C')
I have been able to do the last part as such
SET #Network = '''' + replace(#Network, ',',''',''') + '''';
where rolename in (#Network);`
You can use:
where #network in (select s.value from string_split(#network, ',') s) or
concat(',', #network, ',') like '%,showall%,'
IN doesn't work like that, WHERE rolename IN ('A,B,C') would be equivalent to WHERE rolename = 'A,B,C'. IN takes a list of scalar values (e.g. 'A','B','C'), not a delimited scalar value (e.g. 'A,B,C').
Assuming you're using a modern version of SQL Server, then you would need to use STRING_SPLIT to work with a delimited scalar valueL
WHERE rolename IN (SELECT value FROM STRING_SPLIT(#Network,','))
If, however, you want to pass a "show all" parameter, then normally you're better off passing NULL:
WHERE (rolename IN (SELECT value FROM STRING_SPLIT(#Network,','))
OR #Network IS NULL)
If you must pass 'showall' make that the value of #Network, not 'showall,A,B,C', and then just replace #Network IS NULL with #Network = 'showall'
Note that with queries like this, you are likely to suffer poor query plan caching. If you query is as simple as the above WHERE clauses, then simply adding OPTION (RECOMPILE) will get around this; as it forces the data engine to recreate the query plan each time. If you're query is more complex, however, then you would actually be better off going down a dynamic query route, and only including the rolename IN (SELECT value FROM STRING_SPLIT(#Network,',') clause when needed.

Passing in parameter to where clause using IS NULL or Coalesce

I would like to pass in a parameter #CompanyID into a where clause to filter results. But sometimes this value may be null so I want all records to be returned. I have found two ways of doing this, but am not sure which one is the safest.
Version 1
SELECT ProductName, CompanyID
FROM Products
WHERE (#CompanyID IS NULL OR CompanyID = #CompanyID)
Version 2
SELECT ProductName, CompanyID
FROM Products
WHERE CompanyID = COALESCE(#CompanyID, CompanyID)
I have found that the first version is the quickest, but I have also found in other tables using a similar method that I get different result sets back. I don't quite understand the different between the two.
Can anyone please explain?
Well, both queries are handling the same two scenarios -
In one scenario #CompanyID contains a value,
and in the second #CompanyID contains NULL.
For both queries, the first scenario will return the same result set - since
if #CompanyId contains a value, both will return all rows where companyId = #CompanyId, however the first query might return it faster (more on that at the end of my answer).
The second scenario, however, is where the queries starts to behave differently.
First, this is why you get different result sets:
Difference in result sets
Version 1
WHERE (#CompanyID IS NULL OR CompanyID = #CompanyID)
When #CompanyID is null, the where clause will not filter out any rows whatsoever, and all the records in the table will be returned.
Version 2
WHERE CompanyID = COALESCE(#CompanyID, CompanyID)
When #CompanyID is null, the where clause will filter out all the rows where CompanyID is null, since the result of null = null is actually unknown - and any query with null = null as it's where clause will return no results, unless ANSI_NULLS is set to OFF (which you really should not do since it's deprecated).
Index usage
You might get faster results from the first version, since the use of any function on a column in the where clause will prevent SQL Server from using any index that you might have on this column.
You can read more about it on this article in MSSql Tips.
Conclusion
Version 1 is better than version 2.
Even if you do not want to return records where companyId is null it's still better to write as WHERE (#CompanyID IS NULL OR CompanyID = #CompanyID) AND CompanyID IS NOT NULL than to use the second version.
It's worth noting that using the syntax ([Column] = #Value OR [Column] IS NULL) is a much better idea than using ISNULL([Column],#Value) = #Value (or using COALESCE).
This is because using the function causes the query to become un-SARGable; so indexes won't be used. The first expression is SARGable, and thus, will perform better.
Just adding this, as the OP states "I have found that the first version is the quickest", and wanted to elaborate why (even though, currently the statement is incomplete, I am guessing this was more due to user error and ignorance).
The second version is not correct SQL (for SQL Server). It needs an operator. Presumably:
SELECT ProductName, CompanyID
FROM Products
WHERE COALESCE(#CompanyID, CompanyID) = CompanyID;
The first version is correct as written. If you have an index on CompanyID, you might find this faster:
SELECT *
FROM Products
WHERE CompanyID = #CompanyID
UNION ALL
SELECT *
FROM Products
WHERE #CompanyID IS NULL;

Ignore other results if a resultset has been found

To start, take this snippet as an example:
SELECT *
FROM StatsVehicle
WHERE ((ReferenceMakeId = #referenceMakeId)
OR #referenceMakeId IS NULL)
This will fetch and filter the records if the variable #referenceMakeId is not null, and if it is null, will fetch all the records. In other words, it is taking the first one into consideration if #referenceMakeId is not null.
I would like to add a further restriction to this, how can I achieve this?
For instance
(ReferenceModelId = #referenceModeleId) OR
(
(ReferenceMakeId = #referenceMakeId) OR
(#referenceMakeId IS NULL)
)
If #referenceModelId is not null, it will only need to filter by ReferenceModelId, and ignore the other statements inside it. If I actually do this as such, it returns all the records. Is there anything that can be done to achieve such a thing?
Maybe something like this?
SELECT * FROM StatsVehicle WHERE
(
-- Removed the following, as it's not clear if this is beneficial
-- (#referenceModeleId IS NOT NULL) AND
(ReferenceModelId = #referenceModeleId)
) OR
(#referenceModeleId IS NULL AND
(
(ReferenceMakeId = #referenceMakeId) OR
(#referenceMakeId IS NULL)
)
)
This should do the trick.
SELECT * FROM StatsVehicle
WHERE ReferenceModelId = #referenceModeleId OR
(
#referenceModeleId IS NULL AND
(
#referenceMakeId IS NULL OR
ReferenceMakeId = #referenceMakeId
)
)
However, you should note that this types of queries (known as catch-all queries) tend to be less efficient then writing a single query for every case.
This is due to the fact that SQL Server will cache the first query plan that might not be optimal for other parameters.
You might want to consider using the OPTION (RECOMPILE) query hint, or braking down the stored procedure to pieces that will each handle the specific conditions (i.e one select for null variables, one select for non-null).
For more information, read this article.
If #referenceModelId is not null, it will only need to filter by
ReferenceModelId, and ignore the other statements inside it. If I
actually do this as such, it returns all the records. Is there
anything that can be done to achieve such a thing?
You can think of using a CASE for good short circuit mechanism
WHERE
CASE
WHEN #referenceModelId is not null AND ReferenceModelId = #referenceModeleId THEN 1
WHEN #referenceMakeId is not null AND ReferenceMakeId = #referenceMakeId THEN 1
WHEN #referenceModelId is null AND #referenceMakeId is null THEN 1
ELSE 0
END = 1

SQL procedure where clause to list records

I have a stored procedure that will query and return records based on if items are available or not. That part is easy, Im simply passing in a variable to check where available is yes or no.
But what I want to know is how do I throw a everything clause in there (i.e available, not available, or everything)?
The where clause right now is
where availability = #availability
The values of availabitility are either 1 or 0, nothing else.
You can use NULL to represent everything.
WHERE (#availability IS NULL OR availability = #availability)
SELECT *
FROM mytable
WHERE #availability IS NULL
UNION ALL
SELECT *
FROM mytable
WHERE availability = #availability
Passing a NULL value will select everything.
This query will use an index on availability if any.
Don't know the type of #availability, but assuming -1 = everything then you could simply do a
where #availability = -1 OR availability = #availability
multiple ways of doing it. Simpliest way is is to set the default value of the #availability to null and then your where clause would look like this
WHERE (#availability IS NULL OR availability = #availability)

SQL inline conditional in Select

I was wondering if something like this was possible:
SELECT name or alt_name FROM users Where userid=somenum
I have also tried this:
SELECT name IF NOT NULL ELSE alt_name ...
If anyone has any insight I'd appreciate it.
If your trying to replace nulls then
select coalesce(name,alt_name)....
it will return first non null value
How about this?
SELECT IsNull(name, alt_name) FROM users Where userid=somenum
Its similar to the Coalesce function, but it only takes two arguments and is easier to spell.
Do you mean something like this? I'm assuming TSQL
SELECT CASE WHEN [name] IS NOT NULL THEN [name] ELSE [alt_name] END AS [UserName]
FROM [Users] WHERE [UserId] = somenum
Not the most efficient answer, but what about this:
SELECT name FROM users WHERE userid=somenum AND name IS NOT NULL
UNION
SELECT altname FROM users WHERE userid=somenum AND name IS NULL
I think this will produce what you are after.
SELECT CASE
WHEN userid = somenum THEN name
ELSE alt_name
END
FROM users
If you're using a database that supports them then one of the simplest ways is to use a user defined function, you'd then have
SELECT udfPreferredName() FROM users
where udfPreferredName() would encapsulate the logic required to choose between the name and alternative_name fields.
One of the advantages of using a function is that you can abstract away the choice logic and apply it in multiple SQL statements wherever you need it. Doing the logic inline using a case is fine, but will usually be (much) harder to maintain across a system. In most RDBMS the additional overhead of the function call will not be significant unless you are handling very large tables