SQL Server Stored Procedure - Executing different queries by CASE - sql-server

I have two parameters for my stored procedure. Based on what the value of Searching_Condition is, the proper column must be searched. In a pseudo code format, it should be something like this
//CASE #Search_Condition
// WHEN 'UserID' THEN SELECT * FROM user_table WHERE UserID LIKE '#Keywords'
// WHEN 'UserName' THEN SELECT * FROM user_table WHERE UserName LIKE '#Keywords'
// WHEN 'UserAddress' THEN SELECT * FROM user_table WHERE UserAddress LIKE '#Keywords'
The following is the code I was working on and where got stuck. It should be simple but man... for being not familiar with SQL Server, I'm so struggling with it and CASE in SQL Server doesn't work the way I thought it would.
Thanks !
CREATE PROCEDURE [dbo].[USP_SP_NAME]
#Searching_Condition NVARCHAR(100),
#Keywords NVARCHAR(100)
AS
SET NOCOUNT ON
SET LOCK_TIMEOUT 3000
SET XACT_ABORT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRY
SELECT
CASE WHEN
#Searching_Condition = 'user_id' THEN
(select count(*) from user_table)
WHEN
#Searching_Condition = 'user_name' THEN
(select * from user_table)
END
END TRY

The key concept that will help you get this right is the difference between expressions and statements.
A statement is procedural and directs the flow of control. You can think of an instruction pointer proceeding from statement to statement, and every statement is isolated from other statements (although they can select which statements after them are executed or not). They can be thought of as verbs.
An expression is something that reduces to a value--a scalar value, a string, or even a rowset--but the expression doesn't command to DO anything. They can be thought of as nouns. These nouns can't exist by themselves, they must be in the context of a statement.
The CASE statement in SQL Server is an expression. It isn't a procedural statement like Select Case is in, for example, Visual Basic. And the trick is, when the language expects an expression, you cannot substitute a statement--and furthermore, except in some special usages, you can't put procedural statements in the middle of expressions (except rowsets that can be evaluated as an expression, such as a single-column and single-row SELECT, or an EXISTS). An expression can contain expressions which contain expressions. They're like a tree, that is collapsed in order all the way down.
Think of the parts in EXECUTE dbo.MyStoredProcedure (8 + ##SPID) / 2: this is a single statement, with one parameter expression, consisting of three sub-expressions, evaluated in a certain order, that resolve to a single value, which is used as an argument to the stored procedure. You could not execute (8 + ##SPID) / 2 by itself, because it isn't a statement. (Never mind that the expression is silly, it is just for example.)
I did say that in some cases rowsets can be values, but the expected type of almost all expressions is a single value--not a rowset. That's the problem that's happening here--your outer SELECT statement is expecting a single value for the definition of the first column in a single row (since you have no FROM clause), but you're trying to provide a whole rowset when your searching condition is 'user_name'.
You can solve this by abandoning CASE entirely and using IF--because IF is a procedural statement.
CREATE PROCEDURE [dbo].[USP_SP_NAME]
#Searching_Condition NVARCHAR(100),
#Keywords NVARCHAR(100)
AS
SET NOCOUNT ON;
SET LOCK_TIMEOUT 3000;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN TRY
IF #Searching_Condition = 'user_id' BEGIN
select count(*) from user_table;
END
ELSE IF #Searching_Condition = 'user_name' BEGIN
select * from user_table;
END;
END TRY;
I advocate avoiding the version of the IF that doesn't use BEGIN and END and accepts a single statement--this form leads to confusion and bugs. I use BEGIN and END every time, which seems like a pain, until you discover how much time and effort doing so saves you down the road...

You can try this, no need to give cases, where condition will change depending on the value of #Searching_Condition:
CREATE PROCEDURE [dbo].[USP_SP_NAME]
#Searching_Condition NVARCHAR(100),
#Keywords NVARCHAR(100)
AS
SET NOCOUNT ON
SET LOCK_TIMEOUT 3000
SET XACT_ABORT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRY
exec('Select * from user_table WHERE'+ #Searching_Condition+' LIKE '+ #Keywords);
END TRY

Related

Why do we use the SET NOCOUNT ON; along with the SELECT ##ROWCOUNT;?

I am learning through the EF Core through the tutorials over here and I come across the following statements being used in the examples of the T-SQL queries:
1 SET NOCOUNT ON; Meaning (from here):
Stops the message that shows the count of the number of rows affected by a Transact-SQL statement or stored procedure from being returned as part of the result set.
2 SELECT ##ROWCOUNT; Meaning (from here)
Returns the number of rows affected by the last statement.
The 1 and 2 are used simultaneously. And that is confusing for me. For instance, here:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Books] SET [AuthorId] = #p0
WHERE [BookId] = #p1;
SELECT ##ROWCOUNT;
',N'#p1 int,#p0 int',#p1=4,#p0=1
I can not understand why would someone want to turn off the effect of the 2 with the help of 1 if it is possible to avoid the effect of the 2 by just not adding it into the query. I.e. the following query will do exactly the same as the above one:
exec sp_executesql N'UPDATE [Books] SET [AuthorId] = #p0
WHERE [BookId] = #p1;
',N'#p1 int,#p0 int',#p1=4,#p0=1
Am I missing something here or what is the purpose of using both the SET NOCOUNT ON and the SELECT ##ROWCOUNT simultaneously?
SET NOCOUNT ON stops the results from being printed on the console (screen).
The use of ##ROWCOUNT captures the row count as a parameter in T-SQL and makes it possible to use it for further processing. For instance, you could have conditional logic to do something if no rows are updated.
SET NOCOUNT is often used is suppress the DONE_IN_PROC TDS protocol messages (row counts) SQL Server would otherwise return to the client over the underlying TDS protocol stream. These messages can cause issues when using some SQL Server APIs unless the application is specifically coded to handle them (e.g. by invoking ADODB.Recordset.NextRecordSet) to ensure subsequent statements in the batch are executed and result sets returned. Consequently, use of SET NOCOUNT ON has become a common practice with SQL Server unless the row counts are actually needed by the application.
An additional benefit, called out in the doc reference in your question, is a performance benefit in cases where the batch contains many SQL statements. SET NOCOUNT eliminates the overhead of returning the messages that would be discarded anyway.

SQL injection with parameterised procedures

need a bit of help with this sql injection issue:
The following is a version of a parameterised stored procedure. Excluding how it is called from an application, is there anyway to prevent #v_string from being treated as dynamic SQL?
I think this is fairly water tight - there's no execute or concatenated sql, but still inserting a semicolon allows additional data to be returned.
I know there are multiple levels to consider this question on, but I want to know if there is some simple solution I am missing here as the majority of injection fixes involve dynamic queries.
create table dbo.Employee (EmpID int,EmpName varchar(60))
declare
#v_id int,
#v_string varchar(60)
begin
set #v_string='test'''; waitfor delay '0:0:5' --
if #v_id is null
begin
set #v_id = (select EmpID
from Abc.Employee
where EmpName=#v_string);
end
print #v_id
end
is there anyway to prevent #v_string from being treated as dynamic
SQL?
I would not expect #v_string to be treated as dynamic SQL here since the T-SQL code has no EXECUTE or EXECUTE sp_executeSQL. The value will not be executed, but treated as a WHERE clause value not vulnerable to SQL injection.
If this doesn't answer your question, post a full example that demonstrates the value being treated as dynamic SQL.
You're being confused by your own testing. The line:
set #v_string='test'''; waitfor delay '0:0:5' --
Is creating a string #v_string with the value test', and then executing waitfor delay '0:0:5'. Then your actual Employee query is being run.
So if you run your query as is, with your additional example:
set #v_string='test'''; select * from sys.databases
...what will happen is that line of code will set #v_string to be test', then immediately execute select * from sys.databases. Then the rest of your code will run, executing your actual select. So you'll see the result of select * from sys.databases, followed by the result of your Employee query, but only because you actually hard-coded the statement select * from sys.databases into your procedure without realising it :)
If you want the string #v_string to be set to test'; waitfor delay '0:0:5' then you've got the string quoting wrong. It should be:
set #v_string='test''; waitfor delay ''0:0:5'''

Short circuit in IF clause

I have searched and found nothing about it (I believe it is impossible to do it). My problem is that I have to check if a temporary table exists and also if there is some specific data on that temporary table.
Did anyone faced this before? How did you managed to solve it? I would like to avoid creating milions of IF..ELSE blocks.
EDIT:
IF (OBJECT_ID('tempdb..#tempTable') IS NOT NULL
AND EXISTS (SELECT * FROM #tempTable WHERE THIS_COLUMN = 'value'))
BEGIN
PRINT 'Temp table and data exists'
END
ELSE
BEGIN
PRINT 'Temp table or data does not exist'
END
This is what I want to do. The problem comes when the tempTable doesn't exist (that could happen). It throws an error because, although the first stamement returns false, it continues to execute the second statement. And the SELECT statement is not able to find the table and therefore throws the error. The solution I found was to do this:
IF OBJECT_ID('#tempTable') IS NOT NULL
BEGIN
IF EXISTS (SELECT * FROM #tempTable WHERE THIS_COLUMN = 'value'
BEGIN
PRINT 'Temp table and data exists'
END
ELSE
BEGIN
PRINT 'Temp table exists but data does not exist'
END
END
ELSE
BEGIN
PRINT 'Temp table does not exist'
END
My question would be, is there a way of having 2 conditions and if the first condition returns false not check the second one? Kind of using && in a programming language.
What you are trying to do is not possible as this is a compile time failure and the whole statement needs to be compiled together.
It won't evaluate the first part of the statement then compile the second part only if that is true. You need to split the test for existence and the query referencing the table into two separate statements so they are compiled separately.
See here
There is no such thing as XAND logical gate (exclusive AND). In theory XAND would mean, that both operands are true or both are false. So this means XAND is the same as Equals (=), at least for bitwise logical operations.
Please show some sample code to illustrade what you are trying to do.
Regards
I have searched this sometime ago and if I remember correctly Sql Server does indeed short circuit logical conditions but it is it who decides which one it will check first regardless of the order in which they appear in the if clause.
This is probably a sketchy solution, but I sometimes use COALESCE statements to control the sorts of if, else, then structure you are trying to get at. In this case, it is a little more dirty looking because we are looking for the inverse of a coalesce statement.
DECLARE #temp_message AS varchar(100)
SELECT #temp_message = COALESCE(CASE
WHEN OBJECT_ID('tempdb..#tempTable') IS NOT NULL THEN NULL
ELSE 'Temp table does not exist'
END,
CASE
WHEN EXISTS (SELECT * FROM #tempTable WHERE THIS_COLUMN = 'value') THEN NULL
ELSE 'Specified value does not exist in temp table'
END,
'Temp table and data exists')
PRINT #temp_message
The COALESCE runs one statement after another until one does not yield a NULL value. That means you can do cool things like run a series of small queries to check some values before running a large costly query. Let me know if that is really illegitimate! It worked on my machine :)
I see two ways to get close to this in MSSQL:
First. If you use sp_executesql (dynamic sql) your stored procedure will be compiled without errors. Also if #tempTable doesn't exists server will output error but continue batch execution:
exec sp_executesql N'SELECT count(*) FROM #tempTable WHERE THIS_COLUMN = ''value'''
if ##rowcount > 0
print 'ok'
else
print 'error'
end;
Second. Just create User defined function with nested IF and EXISTS (as you do it now) which output 0 and 1. And use dynamic sql to input table name and possible filter value(s) to this UDF. In this case you can use this UDF in IF.

SQL Server - Implementing sequences

I have a system which requires I have IDs on my data before it goes to the database. I was using GUIDs, but found them to be too big to justify the convenience.
I'm now experimenting with implementing a sequence generator which basically reserves a range of unique ID values for a given context. The code is as follows;
ALTER PROCEDURE [dbo].[Sequence.ReserveSequence]
#Name varchar(100),
#Count int,
#FirstValue bigint OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- Ensure the parameters are valid
IF (#Name IS NULL OR #Count IS NULL OR #Count < 0)
RETURN -1;
-- Reserve the sequence
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
-- Get the sequence ID, and the last reserved value of the sequence
DECLARE #SequenceID int;
DECLARE #LastValue bigint;
SELECT TOP 1 #SequenceID = [ID], #LastValue = [LastValue]
FROM [dbo].[Sequences]
WHERE [Name] = #Name;
-- Ensure the sequence exists
IF (#SequenceID IS NULL)
BEGIN
-- Create the new sequence
INSERT INTO [dbo].[Sequences] ([Name], [LastValue])
VALUES (#Name, #Count);
-- The first reserved value of a sequence is 1
SET #FirstValue = 1;
END
ELSE
BEGIN
-- Update the sequence
UPDATE [dbo].[Sequences]
SET [LastValue] = #LastValue + #Count
WHERE [ID] = #SequenceID;
-- The sequence start value will be the last previously reserved value + 1
SET #FirstValue = #LastValue + 1;
END
COMMIT TRANSACTION
END
The 'Sequences' table is just an ID, Name (unique), and the last allocated value of the sequence. Using this procedure I can request N values in a named sequence and use these as my identifiers.
This works great so far - it's extremely quick since I don't have to constantly ask for individual values, I can just use up a range of values and then request more.
The problem is that at extremely high frequency, calling the procedure concurrently can sometimes result in a deadlock. I have only found this to occur when stress testing, but I'm worried it'll crop up in production. Are there any notable flaws in this procedure, and can anyone recommend any way to improve on it? It would be nice to do with without transactions for example, but I do need this to be 'thread safe'.
MS themselves offer a solution and even they say it locks/deadlocks.
If you want to add some lock hints then you'd reduce concurrency for your high loads
Options:
You could develop against the "Denali" CTP which is the next release
Use IDENTITY and the OUTPUT clause like everyone else
Adopt/modify the solutions above
On DBA.SE there is "Emulate a TSQL sequence via a stored procedure": see dportas' answer which I think extends the MS solution.
I'd recommend sticking with the GUIDs, if as you say, this is mostly about composing data ready for a bulk insert (it's simpler than what I present below).
As an alternative, could you work with a restricted count? Say, 100 ID values at a time? In that case, you could have a table with an IDENTITY column, insert into that table, return the generated ID (say, 39), and then your code could assign all values between 3900 and 3999 (e.g. multiply up by your assumed granularity) without consulting the database server again.
Of course, this could be extended to allocating multiple IDs in a single call - provided that your okay with some IDs potentially going unused. E.g. you need 638 IDs - so you ask the database to assign you 7 new ID values (which imply that you've allocated 700 values), use the 638 you want, and the remaining 62 never get assigned.
Can you get some kind of deadlock trace? For example, enable trace flag 1222 as shown here. Duplicate the deadlock. Then look in the SQL Server log for the deadlock trace.
Also, you might inspect what locks are taken out in your code by inserting a call to exec sp_lock or select * from sys.dm_tran_locks immediately before the COMMIT TRANSACTION.
Most likely you are observing a conversion deadlock. To avoid them, you want to make sure that your table is clustered and has a PK, but this advice is specific to 2005 and 2008 R2, and they can change the implementation, rendering this advice useless. Google up "Some heap tables may be more prone to deadlocks than identical tables with clustered indexes".
Anyway, if you observe an error during stress testing, it is likely that sooner or later it will occur in production as well.
You may want to use sp_getapplock to serialize your requests. Google up "Application Locks (or Mutexes) in SQL Server 2005". Also I described a few useful ideas here: "Developing Modifications that Survive Concurrency".
I thought I'd share my solution. I doesn't deadlock, nor does it produce duplicate values. An important difference between this and my original procedure is that it doesn't create the queue if it doesn't already exist;
ALTER PROCEDURE [dbo].[ReserveSequence]
(
#Name nvarchar(100),
#Count int,
#FirstValue bigint OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
IF (#Count <= 0)
BEGIN
SET #FirstValue = NULL;
RETURN -1;
END
DECLARE #Result TABLE ([LastValue] bigint)
-- Update the sequence last value, and get the previous one
UPDATE [Sequences]
SET [LastValue] = [LastValue] + #Count
OUTPUT INSERTED.LastValue INTO #Result
WHERE [Name] = #Name;
-- Select the first value
SELECT TOP 1 #FirstValue = [LastValue] + 1 FROM #Result;
END

How do you write a recursive stored procedure

I simply want a stored procedure that calculates a unique id (that is separate from the identity column) and inserts it. If it fails it just calls itself to regenerate said id. I have been looking for an example, but cant find one, and am not sure how I should get the SP to call itself, and set the appropriate output parameter. I would also appreciate someone pointing out how to test this SP also.
Edit
What I have now come up with is the following (Note I already have an identity column, I need a secondary id column.
ALTER PROCEDURE [dbo].[DataInstance_Insert]
#DataContainerId int out,
#ModelEntityId int,
#ParentDataContainerId int,
#DataInstanceId int out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
WHILE (#DataContainerId is null)
EXEC DataContainer_Insert #ModelEntityId, #ParentDataContainerId, #DataContainerId output
INSERT INTO DataInstance (DataContainerId, ModelEntityId)
VALUES (#DataContainerId, #ModelEntityId)
SELECT #DataInstanceId = scope_identity()
END
ALTER PROCEDURE [dbo].[DataContainer_Insert]
#ModelEntityId int,
#ParentDataContainerId int,
#DataContainerId int out
AS
BEGIN
BEGIN TRY
SET NOCOUNT ON;
DECLARE #ReferenceId int
SELECT #ReferenceId = isnull(Max(ReferenceId)+1,1) from DataContainer Where ModelEntityId=#ModelEntityId
INSERT INTO DataContainer (ReferenceId, ModelEntityId, ParentDataContainerId)
VALUES (#ReferenceId, #ModelEntityId, #ParentDataContainerId)
SELECT #DataContainerId = scope_identity()
END TRY
BEGIN CATCH
END CATCH
END
In CATCH blocks you must check the XACT_STATE value. You may be in a doomed transaction (-1) and in that case you are forced to rollback. Or your transaction may had already had rolled back and you should not continue to work under the assumption of an existing transaction. For a template procedure that handles T-SQL exceptions, try/catch blcoks and transactions correctly, see Exception handling and nested transactions
Never, under any languages, do recursive calls in exception blocks. You don't check why you hit an exception, therefore you don't know if is OK to try again. What if the exception is 652, read-only filegroup? Or your database is at max size? You'll re-curse until you'll hit stackoverflow...
Code that reads a value, makes a decision based on that value, then writes something is always going to fail under concurrency unless properly protected. You need to wrap the SELECT and INSERT in a transaction and your SELECT must be under SERIALISABLE isolation level.
And finally, ignoring the blatantly wrong code in your post, here is how you call a stored procedure passing in OUTPUT arguments:
exec DataContainer_Insert #SomeData, #DataContainerId OUTPUT;
Better yet, why not make UserID an identity column instead of trying to re-implement an identity column manually?
BTW: I think you meant
VALUES (#DataContainerId + 1 , SomeData)
Why not use the:
NewId()
T SQL function? (assuming sql server 2005/2008)
that sp will never ever do a successful insert, you have an identity property on the DataContainer table but you are inserting the ID, in that case you will need to set identity_insert on but then scope_identity() won't work
A PK violation also might not be trapped so you might also need to check for XACT_STATE()
why are you messing around with max, use scope_identity() and be done with it

Resources