SQL server Where Clause variable may be null - sql

I have the following query (SQL server):
DECLARE #UserId INT;
SET #UserId = //... set by dynamic variable
SELECT *
FROM Users
WHERE userId = #UserId
My issue is that if the #UserId is null the query will not evaluate correctly.
How can I write this query to evaluate correctly if the variable is null or not null?
EDIT:
There have been many suggestions to use the following:
WHERE (#UserId IS NULL OR userId = #UserId)
OR similar.
In this case, if there is a table of 3 entries, with userId of 1,2 and 3 the variable '#UserId' IS NULL, this query will return all 3 entries. What I actually need it to return is no entries, as none of them have a userId of NULL

You need to use an OR:
DECLARE #UserId INT;
SET #UserId = //... set by dynamic variable
SELECT *
FROM Users
WHERE (userId = #UserId OR #UserId IS NULL);
This, however, could well have (severe) performance issues if you're writing this in a Stored Procedure, reusing this code a lot or adding more NULLable parameters. If so, include OPTION (RECOMPILE) in your query so that the query plan is generated each time it's run. This will stop the Data Engine using query plans generated that had a different set of NULL parameters.
Edit: The OP wasn't clear on their question. They don't want to pass the value NULL for #UserID and return all rows, they want to pass NULL and get rows where UserID has a value of NULL. That would be:
SELECT *
FROM Users
WHERE UserID = #UserID
OR (UserID IS NULL AND #UserID IS NULL);

After reading the edit, i think you want your query like
SELECT *
FROM Users
WHERE COALESCE(userId ,0) = COALESCE(#UserId,0)
Edit:
As pointed by Gordon Linoff & Larnu that above query will not be good in terms of performance as the query is "non-SARGable", for the better performance same query can be written as
SELECT *
FROM Users
WHERE userId = #UserId OR( userId is null and #UserId is null)

use coalesce
SELECT *
FROM Users
WHERE userId = coalesce(#UserId,val)

You can simplify the Boolean logic instead :
WHERE (#UserId IS NULL OR userId = #UserId)

Try this
DECLARE #UserId INT;
SET #UserId = //... set by dynamic variable
SELECT *
FROM Users
WHERE userId= (case when #UserId is null then userId else #UserId end)

I haven't seen the use of INTERSECT suggested so far so putting this out there as an alternative that also results in easy to read SQL when your list of potentially NULL variables might be long.
SELECT *
FROM Users
WHERE EXISTS (
SELECT UserId
INTERSECT SELECT #UserId
)
This pattern is useful when you have other variables to check e.g.
SELECT *
FROM Users
WHERE EXISTS (
SELECT
UserId,
Username,
UserRole
INTERSECT SELECT
#UserId,
#Username,
#UserRole
)

Related

SQL Stored procedure querying multiple tables syntax

I am currently trying to create a stored procedure where it first grabs the RUId(Registered User ID) from the first table.And then uses this variable to query another table
The easiest way to explain this is by showing the a pseudo code of this request as shown below
create procedure GetRUIdForUser
#Email nvarchar (160)
AS
SELECT RUId From RegisteredUsers
WHERE Email = #Email
Then
Select * From OtherTable where Ruid = #Ruid
What would the correct syntax for this be or would this need to be split up into two separate stored procedures?
Thanks!
Why not simply use JOIN ? instead of multiple variable & select statements :
select t.*
from RegisteredUsers ru inner join
table t
on t.Ruid = ru.RUId
where ru.Email = #Email;
However, your query variable will have only one ruid which will not help you more. So, you need a table variable to hold all ruids instead.
Try this:
SELECT *
FROM OtherTable
WHERE Ruid = ( SELECT RUId
FROM RegisteredUsers
WHERE Email = #Email ) ;
I recommend a join but you need to declare a variable to use what you are trying to do.
create procedure GetRUIdForUser #Email nvarchar (160) as
declare #ruid int
SELECT #ruid = RUId From RegisteredUsers
WHERE Email = #Email
Select * From OtherTable where Ruid = #Ruid
My guess is that problem isn't as simple as you summarized.

How to return default value from SQL query

Is there any easy way to return single scalar or default value if query doesn't return any row?
At this moment I have something like this code example:
IF (EXISTS (SELECT * FROM Users WHERE Id = #UserId))
SELECT Name FROM Users WHERE Id = #UserId
ELSE
--default value
SELECT 'John Doe'
How to do that in better way without using IF-ELSE?
Assuming the name is not nullable and that Id is unique so can match at most one row.
SELECT
ISNULL(MAX(Name),'John Doe')
FROM
Users
WHERE
Id = #UserId
Try ISNULL or COALESCE:
SELECT ISNULL((SELECT TOP 1 Name FROM Users WHERE Id = #UserId), 'John Doe')
The inner select will return nothing if no user exist with this id, the isnull will solve this case.
Try this
SELECT IFNULL(Name,'John Doe')
FROM Users
WHERE Id = #UserId)
You can drop the if statement using following construct but that doesn't necessarely mean it is better.
SELECT Name FROM Users WHERE Id = #UserId UNION ALL
SELECT 'John Doe' WHERE NOT EXISTS (SELECT Name FROM Users WHERE Id = #UserId)
Try isnull
SELECT IsNULL(Name, 'John Doe') FROM Users WHERE Id = #UserId
Edit:
drop table users
go
create table users
(id int,name varchar(20))
go
insert into users select 1,'1'
go
declare #userid int
set #userid = 1
select isnull(username.username, 'John Doe')
from (select #userid as userid) userid
outer apply (SELECT name as username FROM Users WHERE Id = userid.userid ) username
--outer apply (SELECT name as username FROM Users WHERE Id = #userid ) username
I suppose you could use ##ROWCOUNT to see if any will be returned.
SELECT Name FROM Users WHERE Id = #UserId
if(##ROWCOUNT = 0)
SELECT 'John Doe'
You could also use a variable if you're expecting one row.
declare #name varchar(100)
set #name = (select top 1 name from users where id = #userId)
if(#name is null) set #name = 'John Doe'
select #name
I would suggest that the best way to do is that first declare #name . Then set this value based on user id and then if #name is null show default name otherwise show name... That method would be as efficient as any other method and will be more readable.for other methods any other user to have think a lot to know what is going on unless there is nice comment.
declare #userid int
set #userid = 1
select isnull(
(select name from users where id = #userid),
'John Doe'
)
go
--My preffered would be this one..
declare #name varchar(20),#userid int
set #userid = 1
select #name = name from users where id = #userid
select isnull(#name,'John Doe')
If the query is supposed to return at most one row, use a union with the default value then a limit:
SELECT * FROM
(
SELECT Name FROM Users WHERE Id = #UserId`
UNION
SELECT 'John Doe' AS Name --Default value
) AS subquery
LIMIT 1
Both the query and default can have as many columns as you wish, and you do not need to guarantee they are not null.

UDF inside an SP performance issue on ms sql

I have the following SP
PROCEDURE [dbo].[order_s]
(
#user Uniqueidentifier
)
AS
BEGIN
SET NOCOUNT ON;
SELECT
id,
name,
[begin],
[end]
FROM
orders
WHERE
#user = dbo.hasAccess(#user,id,'select')
END
This SP calls a this UDF
FUNCTION [dbo].[hasAccess]
(
#user uniqueidentifier,
#orderId bigint,
#AccessType nchar(10)
)
RETURNS uniqueidentifier
AS
BEGIN
DECLARE #Result uniqueidentifier
SELECT
Top 1 #Result = [user]
FROM
access
WHERE
orderId = #orderId AND
[user] = #user AND
role >= CASE
WHEN #AccessType = 'select' then 1
WHEN #AccessType = 'insert' then 5
WHEN #AccessType = 'update' then 7
WHEN #AccessType = 'delete' then 10
END
RETURN #Result
END
My question is, calling an UDF from an SP have any performance issues?
Is there a better way to achieve the same functionality?
Thanks for your advise...
Yes this is a bad use of scalar UDFs. This should perform much better.
SELECT
id,
name,
[begin],
[end]
FROM
orders o
WHERE EXISTS(
SELECT *
FROM access
WHERE orderId = o.id AND [user] = #user AND role >= 1
)
Some discussion on Scalar UDFs and performance here
As always with questions re: performance- profile, profile, profile.
Other than that, the only reason I can see that a UDF would cause any performance problems was if it, itself, was particularly inefficient. It should be no less efficient than calling GetDate() in a Stored Proc.

Executing a WHERE clause conditionally in SQL

I have an application on a SQL Server 2008 database. This database has a stored procedure that queries one of the tables. This stored procedure takes two parameters: userName and ID
The userName parameter will always be passed. However, the ID field will either be NULL or an actual value. If the value is something other than NULL, I need to consider it in the WHERE clause of my query. Unfortunately, I'm not positive how to do this. Currently, I'm trying
SELECT
*
FROM
TaskTicket t
WHERE
t.[UserName]=#userName AND
-- This is where I am stumped
Thank you for your help!
SELECT
*
FROM
TaskTicket t
WHERE
t.[UserName]=#userName
AND (#ID IS NULL OR t.[ID] = #ID)
Try this:
SELECT
*
FROM
TaskTicket t
WHERE
t.[UserName]=#userName AND
(#ID is null
or -- replace this comment with your logic
)
Group the conditionals together
select *
from TaskTicket t
Where t.[UserName]=#userName AND
((t.Id is null and (conditions_when_id_is_null))
or
(t.Id is not null and (conditions_when_id_is_not_null)))
SELECT
<column list>
FROM
TaskTicket T
WHERE
T.[UserName] = #username AND
(T.id = #id OR #id IS NULL)
Just be aware that this may cause a non-optimal query plan in some cases. That's probably not a big deal in this case unless your table is huge and you don't have an index on UserName and ID.
Hopefully more efficient than using an OR condition:
SELECT
*
FROM
TaskTicket t
WHERE
t.[UserName]=#userName AND
t.[ID] LIKE COALESCE(#ID,'%')
NB: will only work if ID is a non-NULLable, character field. (You can use CAST and COALESCE on t.[ID] otherwise, but then it's unlikely to be more efficient than an OR condition.)
Alternatively, use dynamic SQL in your stored procedure to completely omit the t.[ID] condition, if #ID is NULL.
declare #SQL nvarchar(max)
declare #WHERE_ID nvarchar(20)
set #WHERE_ID =
(
CASE
WHEN #ID is null THEN ''
ELSE ' AND ID = ' + CAST(#ID as nvarchar(10))
END
)
set #SQL = 'SELECT * FROM TaskTicket WHERE UserName = ' + #userName + #WHERE_ID
EXEC #SQL
Create procedure Procedure1
(
#Param1 nvarchar(100)=null,
)
AS
BEGIN
SELECT
ColumnName1,ColumneName2
FROM TableName
WHERE
(#Param1 IS NULL OR ColumnName1=#Param1)
END

SQL if( exists()) query duplication

I need to write a a query that gets a set of information from a table, but if there is no information for that specific client, then use the default set. I was wondering if there is a way to avoid query duplication in an if(exists()) statement.
For example:
IF( EXISTS( SELECT * FROM UserTable WHERE Name = #UserName))
BEGIN
SELECT * FROM UserTable WHERE Name = #UserName))
END
ELSE
BEGIN
SELECT * FROM UserTable WHERE Name = 'Jon Skeet'))
END
The first two selects (exists and the true part of the if) are the exact same. I want to avoid running the same query twice if the statement is true. I know the exists stops once the first true condition is met but that is still O(n) worst case.
Another option I know of, is put the information in a temp table and check if information is there, if not return the default information.
Both ways would work but what is the best way to do it? Are there other ways to do this? Is there some way to do this in the WHERE clause since that's the only thing that is different?
Edit: Fixed example to return a row not just a single item. So the answers of dumping the select to a single variable would be equal to a temp table I assume. Also, to not anger Jon Skeet, spelled his name right
You could do this:
SELECT TOP 1
UserID
FROM
UserTable
WHERE
Name IN (#UserName, 'John Skeet')
ORDER BY
CASE WHEN Name = 'John Skeet' THEN 2 ELSE 1 END
(or use LIMIT or whatever the method is for your RDBMS)
Or you could do this:
DECLARE #UserID INT
SELECT
#UserID = UserID
FROM
UserTable
WHERE
Name = #UserName
IF (#UserID IS NULL)
SELECT
#UserID = UserID
FROM
UserTable
WHERE
Name = 'John Skeet'
SELECT #UserID AS UserID
Or this:
SELECT
COALESCE(T2.UserID, T1.UserID)
FROM
UserTable T1
LEFT OUTER JOIN UserTable T2 ON
T2.Name = #UserName
WHERE
T1.Name = 'John Skeet'
Or this:
SELECT
UserID
FROM
UserTable
WHERE
Name = #UserName
IF (##ROWCOUNT = 0) -- MS SQL Server specific, your RDBMS method will vary
SELECT
UserID
FROM
UserTable
WHERE
Name = 'John Skeet'
How about:
DECLARE #UserID int;
SELECT #UserID = UserID FROM UserTable WHERE Name = #UserName
IF(#UserID IS NULL)
BEGIN
SELECT #UserID=UserID FROM UserTable WHERE Name = 'Jon Skeet'))
END
SELECT #UserID
You can select into a variable check the variable if it comes back null run the get default query
something like this:
Declare #uID int;
SELECT #uID = UserID FROM UserTable WHERE Name = #UserName
IF (#uID is null)
begin
'Select default here'
End
select #uID
If you are concerned about performance, I think you probably don't need to be - most DBMSes should optimize away the duplication. If you are still concerned about performance, run it both ways to see if your DBMS has any problem with it.
If you're concerned about code - maintainability of duplicate code sections, readability, and the like, then study the above answers with that in mind. You may find that the duplication is your least-worst option. It's a shame SQL isn't more malleable.
You don't say what DBMS you are using, but it clearly isn't Oracle (which is the one I know). However, I would expect your DBMS would allow you to do something like this pseudo-code:
SELECT UserID FROM UserTable WHERE Name = #UserName
IF <no data returned by that>
BEGIN
SELECT UserID FROM UserTable WHERE Name = 'John Skeet'
END
In Oracle this would be:
BEGIN
SELECT UserID INTO v_UserID FROM UserTable WHERE Name = :UserName;
EXCEPTION
WHEN NO_DATA_FOUND THEN
SELECT UserID INTO v_UserID FROM UserTable WHERE Name = 'John Skeet';
END
Unless you are going to define a set of variables for each field you are returning (and it's only one record) I think the most efficent way is to make the call twice. The temp table would probably cost more.