Unable to create procedure with case expression in where clause - sql

Considering this table:
CREATE TABLE RACKRATE
(
RackRateID SMALLINT PRIMARY KEY IDENTITY,
RoomTypeID SMALLINT REFERENCES ROOMTYPE(RoomTypeID) NOT NULL,
HotelID SMALLINT REFERENCES HOTEL(HotelID) NOT NULL,
RackRate SMALLMONEY NOT NULL,
RackRateBegin DATE NOT NULL,
RackRateEnd DATE NOT NULL,
RackRateDescription VARCHAR(200)
);
I'm trying to create a stored procedure to update the table, like so:
CREATE PROCEDURE usp_UpdateRackRates
#hotelid smallint,
#roomtypeid smallint = NULL,
#rackratedescription varchar(200) = NULL,
#percent decimal(6,4)
AS
UPDATE RACKRATE
SET RackRate = CASE
WHEN #percent >= 0
THEN CEILING(RackRate * ((100 + #percent)/100))
ELSE FLOOR(RackRate * ((100 + #percent)/100))
END
WHERE
(CASE
WHEN #roomtypeid IS NOT NULL AND #rackratedescription IS NOT NULL
THEN HotelID = #hotelid AND RackRateDescription = #rackratedescription
WHEN #hotelid IS NOT NULL
THEN HotelID = #hotelid
WHEN #rackratedescription IS NOT NULL
THEN RackRateDescription = #RACKRATEDESCRIPTION
END);
When I run this in SQL Server, I get this error
Incorrect syntax near '='
pointing at the first = sign in the where clause.
As a sanity check, I created the same table in sqlite and checked this same update statement with it, and sqlite worked just fine (obviously just the update statement, sqlite doesn't have stored procedures).
Where am I going wrong here?

SQL Server does not really have a boolean type. (Or more properly, SQL Server treats Boolean in a separate category from other types, with limited usages.) The BIT type is close, but not equivalent.
(See SQLpro's note below regarding ISO SQL standard compliance.)
The following is a direct adaptation of your logic that takes the results of each case condition and emits a 1 or 0. The WHERE clause then checks for an overall result = 1. I also added an ELSE 1 to handle the "nothing to check" case.
WHERE 1 =
(CASE
WHEN #roomtypeid IS NOT NULL AND #rackratedescription IS NOT NULL
THEN CASE WHEN HotelID = #hotelid AND RackRateDescription = #rackratedescription
THEN 1 ELSE 0 END
WHEN #hotelid IS NOT NULL
THEN CASE WHEN HotelID = #hotelid
THEN 1 ELSE 0 END
WHEN #rackratedescription IS NOT NULL
THEN CASE WHEN RackRateDescription = #RACKRATEDESCRIPTION
THEN 1 ELSE 0 END
ELSE 1
END);
There may be ways to reduce this, but I believe the current form reflects your intent.
Side note: Be aware that the above approach may lead to performance issues. The SQL Server query optimizer will not be able to effectively use an index to identify rows to be updated, so a table scan is likely to occur with every execution (or at best a complex index scan).
A better approach that would allow index optimization would be to split the UPDATE into three separate statements, contained within an IF-ELSE structure, and with each having a simplified WHERE condition.
Side note 2: Did you indend to check for #roomtypeid IS NOT NULL and then apply a HotelID = #hotelid condition in the first case branch?

CREATE PROCEDURE usp_UpdateRackRates
#hotelid smallint,
#roomtypeid smallint = null,
#rackratedescription varchar(200) = null,
#percent decimal(6,4)
AS
UPDATE RACKRATE SET RackRate =
CASE WHEN #percent >= 0 THEN CEILING(RackRate * ((100 + #percent)/100))
ELSE FLOOR(RackRate * ((100 + #percent)/100))
END
WHERE (CASE
WHEN #roomtypeid IS NOT NULL AND #rackratedescription IS NOT NULL
THEN HotelID = #hotelid AND RackRateDescription = #rackratedescription
WHEN #hotelid IS NOT NULL THEN HotelID = #hotelid
WHEN #rackratedescription IS NOT NULL THEN RackRateDescription = #rackratedescription
END);

Related

How to use cases inside of where clause?

I am attempting to choose one of the following paths. The user will either enter the customer name (#customer), the supplier name (#supplier), or the part number (#part). When they enter one of those I want to do a search in my table like customer_name=#customer if they choose customer and the same if they choose any of the others. Here is what I am trying.
CREATE PROCEDURE sp_edit_button
(
#customer nvarchar(200) = NULL,
#supplier nvarchar(200) = NULL,
#part nvarchar(100) = NULL
)
AS
BEGIN
SELECT *
FROM REC_INSP_LOG
WHERE (
CASE
WHEN #customer IS NOT NULL THEN customer_name = #customer
WHEN #supplier IS NOT NULL THEN supplier_name = #supplier
WHEN #part IS NOT NULL THEN part_num = #part
)
END
You can phrase this logic without a case expression:
WHERE (#customer IS NULL OR customer_name = #customer) AND
(#supplier IS NULL OR supplier_name = #supplier) AND
(#part IS NULL OR part_num = #part)
That said, the resulting query will not be able to take advantage of any indexes on a table. If that is something you want, then you will need to use conditional logic or dynamic SQL.
That indeed can't work, because you're treading the realm of dynamic SQL. Also note that your CASE is supposed to have an END at the end.
I would suggest taking a look at Gordon's answer or, if you're set on using a case, you could try this:
(CASE
WHEN (#customer IS NOT NULL AND customer_name = #customer)
OR #supplier IS NOT NULL AND supplier_name = #supplier
OR #part IS NOT NULL AND part_num = #part THEN 1
ELSE 0
END) = 1
But you still wouldn't be able to use the index properly.

Generic select doesn't work with bit type

based on this answer
i tryed to create a Select for on of my table
ALTER PROCEDURE _Einrichtung_Select
-- Parameters with default values
#EinrichtungId AS int = NULL,
#EinrichtungName AS nvarchar(50) = NULL,
#IsKueche AS bit = NULL,
#RefEinrichtungId AS int = NULL,
#RefSpeiseplantypId AS int = NULL
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- generic SELECT query
SELECT *
FROM Einrichtung
WHERE EinrichtungId = ISNULL(#EinrichtungId, EinrichtungId)
AND EinrichtungName = ISNULL(#EinrichtungName, EinrichtungName)
AND IsKueche = ISNULL(#IsKueche, IsKueche)
AND RefEinrichtungId = ISNULL(#RefEinrichtungId, RefEinrichtungId)
AND RefSpeiseplantypId = ISNULL(#RefSpeiseplantypId, RefSpeiseplantypId)
ORDER BY EinrichtungName
RETURN
but i got a problem with the bit type example sqlfiddle like you can see it should return 4 rows but it only returns 3 so what do i miss?
It's because you can have null as values of your columns. And SQL have three-value logic, so checking null = null will return UNKNOWN instead of TRUE (as you may expect).
I think this query will help you:
select *
from myTable
where
(#EinrichtungId is null or EinrichtungId = #EinrichtungId) and
(#EinrichtungName is null or EinrichtungName = #EinrichtungName) and
(#IsKueche is null or IsKueche = #IsKueche) and
(#RefEinrichtungId is null or RefEinrichtungId = #RefEinrichtungId) and
(#RefSpeiseplantypId is null or RefSpeiseplantypId = #RefSpeiseplantypId)
sql fiddle demo

conditional stored procedure with/without passing parameter

I created a stored procedure which when passed nothing as parameter should return the entire table. But if the studentId is passed, then return her details.
Something like this
create procedure usp_GetStudents #studentId int = null
as
if (#studentId = null)
select * from Student
else
select * from Student where studentId = #studentId
Output
exec usp_GetStudents -- No records returned though there are records in the table
exec usp_GetStudents #studentId = null -- No records returned
exec usp_GetStudents #studentId = 256 -- 1 entry returned
Just curious to know if anything is wrong in the syntax/logic for returning all the entries of the table?
Thank you
You're trying to test for null using =, a comparison operator. If you're using ANSI nulls, any comparison against null is false.
Where #studentId is any value (or null) the following expressions are all false:
#studentId = null -- false
#studentId > null -- false
#studentId >= null -- false
#studentId < null -- false
#studentId <= null -- false
#studentId <> null -- false
So, in order to test for null you must use a special predicate, is null, i.e.:
#studentId is null
Shorter way to do that:
create procedure usp_GetStudents #studentId int = null
as
select * from Student
where studentId = isnull(#studentId,studentId)
You can't chack if value is null using =.
For your example you have to replace condition #studentId = null to is null syntax.
Try to change your code as below:
create procedure usp_GetStudents #studentId int = null
as
if (#studentId is null)
select * from Student
else
select * from Student where studentId = #studentId
Change the = to an is
create procedure usp_GetStudents #studentId int = null
as
if (#studentId is null)
select * from Student
else
select * from Student where studentId = #studentId

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.

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

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