T-SQL Stored Procedure NULL input values cause select statement to fail - sql

Below is a stored procedure to check if there is a duplicate entry in the database based upon checking all the fields individually (don't ask why I should do this, it just has to be this way).
It sounds perfectly straightforward but the SP fails.
The problem is that some parameters passed into the SP may have a null value and therefore the sql should read "is null" rather than "= null".
I have tried isnull(),case statements,coalesce() and dynamic sql with exec() and sp_executesql and failed to implement any of these. Here is the code...
CREATE PROCEDURE sp_myDuplicateCheck
#userId int,
#noteType char(1),
#aCode char(3),
#bCode char(3),
#cCode char(3),
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE userId = #userId
AND noteType = #noteType
AND aCode = #aCode
AND bCode = #bCode
AND cCode = #cCode
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END

I think you need something like this for each possibly-null parameter:
AND (aCode = #aCode OR (aCode IS NULL AND #aCode IS NULL))

If I understand your question correctly, then I encourage you to do a little research on:
SET ANSI_NULLS OFF
If you use this command in your stored procedure, then you can use = NULL in your comparison. Take a look at the following example code to see how this works.
Declare #Temp Table(Data Int)
Insert Into #Temp Values(1)
Insert Into #Temp Values(NULL)
-- No rows from the following query
select * From #Temp Where Data = NULL
SET ANSI_NULLS OFF
-- This returns the rows where data is null
select * From #Temp Where Data = NULL
SET ANSI_NULLS ON
Whenever you SET ANSI_NULLS Off, it's a good practice to set it back to ON as soon as possible because this may affect other queries that you run later. All of the SET commands only affect the current session, but depending on your application, this could span multiple queries, which is why I suggest you turn ansi nulls back on immediately after this query.

I think this should work with COALESCE function. Try this:
CREATE PROCEDURE sp_myDuplicateCheck
#userId int,
#noteType char(1),
#aCode char(3),
#bCode char(3),
#cCode char(3),
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE userId = #userId
AND noteType = #noteType
AND COALESCE(aCode,'NUL') = COALESCE(#aCode,'NUL')
AND COALESCE(bCode,'NUL') = COALESCE(#bCode,'NUL')
AND COALESCE(cCode,'NUL') = COALESCE(#cCode,'NUL')
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
Good Luck!
Jason

Try this :
CREATE PROCEDURE sp_myDuplicateCheck
#userId int = 0,
#noteType char(1) = "",
#aCode char(3) = "",
#bCode char(3) = "",
#cCode char(3) = "",
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE #userId in (userId ,0)
AND #noteType in (noteType,"")
AND #aCode in (aCode , "")
AND #bCode in (bCode , "")
AND #cCode in (cCode ,"")
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
What this basically does is to provide default values to the input parameters in case of null and then in the where condition checks only if the values are not equal to the default values.

I would first add a check to see if all of the parameters were null at run time, i.e.,
IF(COALESCE(#userId, #noteType, #aCode, #bCode, #cCode) IS NULL)
BEGIN
-- do something here, log, print, return, etc.
END
Then after you've validated that the user passed something in you can use something like this in your WHERE clause
WHERE userId = COALESCE(#userId, userId)
AND noteType = COALESCE(#noteType, noteType)
AND aCode = COALESCE(#aCode, aCode)
AND bCode = COALESCE(#bCode, bCode)
AND cCode = COALESCE(#cCode, cCode)
EDIT: I may have missed the intent that if the parameter was passed in as null that means you explicitly want to test the column for null. My above where clause assumed that the null parameter meant 'skip the test on this column.'
Alternatively, I believe you can use your original query and add the ANSI_NULLS set option at the stored procedure create time. For example,
SET ANSI_NULLS OFF
GO
CREATE PROC sp_myDuplicateCheck....
Effectively this should allow your code to then evaluate column=null as opposed to column is null. I think Kalen Delaney once coined the ANSI_NULLS and QUOTED_IDENTIFIER options as 'sticky options' because if they're set at procedure create time they stay with the procedure at run time, regardless of how the connection at that time is set.

SET ANSI_NULLS OFF/On
That way you can do colName = null

Related

Set parameters in a stored procedure that returns a resultset

I'm trying to consolidate some code but before I open this particular can of worms I wanted to find out from you guys. If I have several stored procedures...
sproc1 - "master proc" which sets #test
sproc2 - proc that executes if #test exists and returns both a resultset and (if possible) resets #serial
sproc3 - proc that executes if #test does not exist and returns both a resultset and (if possible) resets #serial
sproc1
#leftStack INT,
#leftTray INT,
#midStack INT,
#midTray INT,
#rightStack INT,
#rightTray INT
AS
DECLARE #soLineNumber varchar(50)
DECLARE #serial VARCHAR(50)
DECLARE #rack INT
DECLARE #tray INT
DECLARE #position INT
SELECT #test = oL.[SERIAL_NUMBER]
FROM [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_ORDER_OPTICS] AS oL
WHERE NOT EXISTS
(
SELECT [SERIAL_NUMBER]
FROM [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_OPTIC_RESULTS] AS rL
WHERE oL.[SERIAL_NUMBER] = rL.[SERIAL_NUMBER]
)
AND NOT EXISTS
(
SELECT [SERIAL_NUMBER]
FROM [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_OPTIC_INSERTED] AS oI
WHERE oL.[SERIAL_NUMBER] = oI.[SERIAL_NUMBER]
)
-- AND oL.[SO_LINE_NUMBER] = #soLineNumber --pick regardless of SO line number, to reduce gaps between lines
AND ((oL.[RACK] = #leftStack AND oL.[TRAY] = #leftTray)
OR (oL.[RACK] = #midStack AND oL.[TRAY] = #midTray)
OR (oL.[RACK] = #rightStack AND oL.[TRAY] = #rightTray))
ORDER BY [SO_LINE_NUMBER] ASC
IF NULLIF(#test, '') IS NOT NULL
BEGIN
EXEC sproc2
END
IF NULLIF(#test, '') IS NULL
BEGIN
EXEC sproc3
END
UPDATE [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_ORDER_OPTICS] SET [PICKED] = 'True' WHERE [SERIAL_NUMBER] = #serial
END
My questions:
1) how can I reset #serial from sproc2 and sproc3?
2) in an ADO recordset query, will the results from the executed stored procedures pull in, if so, how?
For this to work sproc2 and sproc3 should been defined like this:
CREATE PROC sproc2 #test VARCHAR(50), #serial VARCHAR(50) OUTPUT
What this does is, it sends the value of #test as a value param. The OUTPUT keyword on #serial enables you to keep track of any changes done on #serial.

MSSQL says I am trying to convert a varchar to a string when I'm not

So I have this fairly long procedure at Work that I just made. What it does it not that important, but the end result is what matters.
I need to count some different types of descriptions in a table and that Works fine. I then need to take the two things that I Count and put them in a string that I return to my software. However, every time I run this procedure it gives me this:
Msg 245, Level 16, State 1, Procedure WorkDays, Line 43 Conversion
failed when converting the varchar value
'FlightDeck:161,CabinCrew:189' to data type int.
I just can't figure out why it keeps telling me this when I am not trying to convert a varchar to an int but rather ints to a single varchar.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[WorkDays] #requestedDate nchar(10)
AS
SET ANSI_WARNINGS OFF
DECLARE #date as nchar(10) = ''
DECLARE #returnVal as varchar(30) = ''
DECLARE #flightDeck as int = 0
DECLARE #cabinCrew as int = 0
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET #date = #requestedDate
SELECT
#flightDeck = SUM(CASE WHEN dbo.Crew_Category.Description LIKE 'Flight Deck' THEN 1 END),
#cabinCrew = SUM(CASE WHEN dbo.Crew_Category.Description LIKE 'Cabin Crew' THEN 1 END)
FROM
dbo.CrewMember INNER JOIN
dbo.Crew_Category ON dbo.CrewMember.CrewCategorySeqNo = dbo.Crew_Category.CrewCategorySeqno
WHERE
(dbo.Crew_Category.Description = N'Flight Deck' OR
dbo.Crew_Category.Description = N'Cabin Crew') AND
(dbo.CrewMember.EmploymentEndDate > #date)
AND dbo.CrewMember.CrewSeqno NOT IN (
SELECT
CrewMember_1.CrewSeqno
FROM
dbo.CrewMember AS CrewMember_1 INNER JOIN
dbo.CrewReqAsg ON CrewMember_1.CrewSeqno = dbo.CrewReqAsg.crewSeqno INNER JOIN
dbo.activity ON dbo.CrewReqAsg.act_seqno = dbo.activity.act_seqno INNER JOIN
dbo.ActivityType ON dbo.activity.actType_seqno = dbo.ActivityType.actType_seqno INNER JOIN
dbo.ActivityCategory ON dbo.ActivityType.ActCat_seqno = dbo.ActivityCategory.actCat_seqno INNER JOIN
dbo.Crew_Category AS Crew_Category_1 ON CrewMember_1.CrewCategorySeqNo = Crew_Category_1.CrewCategorySeqno
WHERE (
dbo.ActivityCategory.Category = N'Ferie' OR
dbo.ActivityCategory.Category = N'Fridage' OR
dbo.ActivityCategory.Category = N'Sygdom') AND (Crew_Category_1.Description = N'Flight Deck' OR
Crew_Category_1.Description = N'Cabin Crew') AND (LEFT(dbo.activity.Start,10) LIKE #date));
SET #returnVal = 'FlightDeck:'+CAST(#flightDeck AS varchar);
SET #returnVal += ',CabinCrew:'+CAST(#cabinCrew AS varchar);
END
RETURN #returnVal
It's been a while since I've had to do this so perhaps I just forgot something fundamental. Please help me figure out why this happens? :)
Yes, you forgot something fundamental. To return data to the caller, use SELECT, not RETURN.
You need
SELECT #returnVal

How to Update a row with some declard and setted Values

Hi i struggle with my stored-procedure which adds 'NULL' instead of a number
So why does the following procedure adds 'NULL' instead of a value between 0 and infinity?
Here is my procedure
ALTER PROCEDURE [dbo].[Plan_Abschluss]
-- My parameters for the stored procedure
#date AS datetime2(7),
#Einrichtung AS Int,
#Mitarbeiter AS Int
AS
BEGIN
-- declare my parameters
DECLARE #PlanStunden AS decimal(18, 2)= null,
#PlanUrlaub AS Int= null,
#oldDate AS datetime2(7)= null,
#oldUrlaubskonto AS Int= null,
#oldStundenKonto AS decimal(18, 2)= null;
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- set the previous month
SET #oldDate= DATEADD(month, -1, #date);
-- get some values from the previous row and set it to my parameters
SELECT #oldUrlaubskonto = ISNULL(CurrentUrlaubskonto,0) ,
#oldStundenKonto = ISNULL(CurrentStundenKonto,0)
FROM [Plan]
WHERE [Jahr] = YEAR(#oldDate)
AND [Monat] = MONTH(#oldDate)
AND [RefMitarbeiterId] = #Mitarbeiter
AND [RefEinrichtungId] = #Einrichtung;
-- get some values from the row i want to update and set it to my parameters
SELECT #PlanStunden = ISNULL(PlanStunden,0) ,
#PlanUrlaub = ISNULL(PlanUrlaub,0)
FROM [Plan]
WHERE [Jahr] = YEAR(#date)
AND [Monat] = MONTH(#date)
AND [RefMitarbeiterId] = #Mitarbeiter
AND [RefEinrichtungId] = #Einrichtung;
-- update the row and do a calculation with my parameters
UPDATE [Plan]
SET Abgeschlossen = 1,
CurrentUrlaubskonto = #oldUrlaubskonto+ #PlanUrlaub,
CurrentStundenKonto = #oldStundenKonto+ #PlanStunden
WHERE [Jahr] = YEAR(#date)
AND [Monat] = MONTH(#date)
AND [RefMitarbeiterId] = #Mitarbeiter
AND [RefEinrichtungId] = #Einrichtung
END
Variables will not be set in a select if no rows are returned. My guess is that the first select using #OldDate simply doesn't match any rows.
In particular, the variables #oldUrlaubskonto and #oldStundenKonto are initialized to NULL, so they are never set, when there is no matching record. One easy way to fix this is to use aggregation -- you are expecting one row anyway, so that is okay:
SELECT #oldUrlaubskonto = ISNULL(max(CurrentUrlaubskonto), 0) ,
#oldStundenKonto = ISNULL(max(CurrentStundenKonto), 0
You can also set the value afterwards, if it is still NULL.

SQL Server - check input parameter for null or zero

I have a stored procedure for sql server 2008 like this:
create procedure test_proc
#someval int,
#id int
as
update some_table
set some_column = ISNULL(#someval, some_column)
where id = #id
go
If the parameter #someval is NULL, this SP will just use the existing value in some_column.
Now I want to change this behaviour such that if value for #someval is 0, a NULL is stored in some_column otherwise it behave just the way it is doing now.
So I am looking for something like:
if #someval == 0
set some_column = NULL
else
set some_column = ISNULL(#someval, some_column)
I don't have the option to create a varchar #sql variable and call sq_executesql on it (at least that is the last thing I want to do). Any suggestions on how to go about doing this?
You can do this using the CASE expression. Something like this:
update some_table
set some_column = CASE WHEN #someval = 0 THEN NULL
WHEN #someval IS NULL THEN somcolumn
ELSE #someval -- the default is null if you didn't
-- specified one
END
where id = #id
something like this?
create procedure test_proc
#someval int,
#id int
as
update some_table
set some_column = CASE
WHEN #someval = 0 THEN NULL
ELSE ISNULL(#someval, some_column) END
where id = #id
go
I think it's a really bad idea - I'd suggest that if someone wants to store a NULL, they really shouldn't have to pass some other magical value to cause it to happen. However, let's show how it can be done:
update some_table
set some_column = CASE WHEN #someVal = 0 THEN NULL ELSE ISNULL(#someval, some_column) END
where id = #id
Given the simplicity of the stored procedure in your question, of course, the whole matter can be cleared up by not calling the stored procedure if you don't want to alter some_column. I'd imagine that your real procedure is more complex. Instead, what I'd do is have:
create procedure test_proc
#someval int,
#someval_specified bit,
#id int
as
update some_table
set some_column = CASE WHEN #someval_specified = 1 THEN #someval ELSE some_column END
where id = #id
And now NULL means NULL, 0 means 0, etc.

SQL Server optimize code in Stored Procedure

Comparing two codes below,both do the same,but with slighty differences:
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255),
#usu_ds_email varchar(100)
as
begin
declare #usu_ID int;
create table #TempUser
(
UsuID int,
Senha varchar(255),
Email varchar(100)
)
select Usuarios.usu_ID as UsuID,Usuarios.usu_ds_senha as Senha,
Usuarios.usu_ds_email as Email into #TempUser from Usuarios where Usuarios.usu_ds = #usu_ds
if(#usu_ds_senha is null)
begin
set #usu_ds_senha = (select #TempUser.Senha from #TempUser);
end
if(#usu_ds_email is null)
begin
set #usu_ds_email = (select #TempUser.Email from #TempUser);
end
set #usu_ID = (select #TempUser.UsuID from #TempUser);
update Usuarios set usu_dt_lst_log =
#usu_dt_lst_log,usu_ds_senha = #usu_ds_senha,usu_ds_email = #usu_ds_email where usu_ID = #usu_ID
end
AND
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255),
#usu_ds_email varchar(100)
as
begin
declare #usu_ID int;
if(#usu_ds_senha is null)
begin
set #usu_ds_senha = (select Usuarios.usu_ds_senha from Usuarios where Usuarios.usu_ds = #usu_ds);
end
if(#usu_ds_email is null)
begin
set #usu_ds_email = (select Usuarios.usu_ds_email from Usuarios where Usuarios.usu_ds = #usu_ds);
end
set #usu_ID = (select Usuarios.UsuID from Usuarios where Usuarios.usu_ds = #usu_ds);
update Usuarios set usu_dt_lst_log =
#usu_dt_lst_log,usu_ds_senha = #usu_ds_senha,usu_ds_email = #usu_ds_email where usu_ID = #usu_ID
end
Do you think the first is faster than second in performance,i mean,first code use temp table (#TempUser) to store 3 fields from the real table.Second code,select all fields from the real table one by one.
What code is best optimized?
First -- if it's possible for your passed parameters to be null, you need to set defaults. For example:
#usu_ds_email varchar(100) = null
...
Otherwise, your null checks further down will never come into play -- the procedure will just fail.
Second -- just run a direct update. It seems like you're pushing a lot of data back and forth unnecessarily. E.g., you don't need to create a temp table from the table you're going to update, and then turn right around and update your table from the temp table you just created.
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255) = null,
#usu_ds_email varchar(100) = null
as
begin
update Usuarios
set usu_dt_lst_log = #usu_dt_lst_log,
usu_ds_senha = isnull(#usu_ds_senha, usu_ds_senha),
usu_ds_email = isnull(#usu_ds_email, usu_ds_email)
where usu_ID = #usu_ds
end
Third way...
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255) = null,
#usu_ds_email varchar(100) = null
as
begin
update x
set x.usu_dt_lst_log = #usu_dt_lst_log,
x.usu_ds_senha = ISNULL(#usu_ds_senha, x.usu_ds_senha),
x.usu_ds_email = ISNULL(#usu_ds_email, x.usu_ds_email)
from Usuarios x where x.usu_ds = #usu_ds
end
Which one runs faster? Set a profiler, run both, and get some real data: then you'll know.
However, based on my own previous experiences, the Temp Table has always been faster for me.