Conditional IN clause in SQL Server [duplicate] - sql-server

This question already has answers here:
T-SQL stored procedure that accepts multiple Id values
(5 answers)
Closed 4 years ago.
I have got a stored procedure which has five parameters. I want to include them in where clause as follows
If parameter is not null then include it in query as an IN clause
That is, parameter value can be like 'Test' or 'Test,Best' etc. I'm converting this comma seperated values into table using function in SQL.
I tried to use COALESCE(#test,test_column) = test_column but i'm unable to include IN clause here (What if #test = 'Test,Best').
So, i want to do something like mentioned below
DECLARE #param varchar(max) = 'Test,Best';
Select * from table where CASE when #param is not null then table.column in (#param)
Any suggestions please.

I've adapted this answer to your requirements.
First create User-Defined Table type:
CREATE TYPE dbo.ParamsList
AS TABLE
(
Param varchar(50)
);
Then just use this parameter and other parameters in your stored procedures:
CREATE PROCEDURE dbo.DoSomethingWithEmployees
#List AS dbo.ParamsList READONLY
, #param1 INT = NULL
, #param2 NVARCHAR(150) = NULL
, #param3 NVARCHAR(150) = NULL
, #param4 NVARCHAR(150) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT
*
FROM YourTable
WHERE YourColumn NOT IN (SELECT Param FROM #List)
END
GO

Related

IF statement using output of SPROC SQL [duplicate]

This question already has answers here:
TSQL: Call a stored procedure from another stored procedure and read the result
(5 answers)
Closed 4 years ago.
I am creating stored procedures (sprocs) to perform operations on my tables in my database. I have a sproc SubjectExists that returns '1' if the subject name entered is in the subject table and returns '0' if it does not exist.
CREATE PROCEDURE SubjectExists #SubjName varhcar(20)
AS
SELECT CASE WHEN EXISTS(
SELECT *
FROM Subject
WHERE Subject_Name = #SubjName
)
THEN CAST (1 AS BIT)
ELSE CAST (0 AS BIT)
END
I am now making another sproc that deletes a subject from the table. I want to make this sproc such that it uses SubjectExists and if the output of it is a 1 (i.e. the subject does exist) then it deletes the subject and if the output from SubjectExists is 0, it does nothing.
How would I go about doing this?
I have tried experimenting with the below but no luck so far.
CREATE PROCEDURE DeleteSubject #SubjName varchar(20)
AS
IF (EXEC StudentExists Bob)
DELETE FROM Subject
WHERE Subject_Name = #SubjName;
Can anyone please guide me as to how I would do this.
Thanks
At first, your stored procedure should return value:
CREATE PROCEDURE SubjectExists #SubjName varchar(20)
AS
BEGIN
DECLARE #ReturnValue int
SELECT #ReturnValue = CASE WHEN EXISTS(
SELECT *
FROM Subject
WHERE Subject_Name = #SubjName
)
THEN CAST (1 AS BIT)
ELSE CAST (0 AS BIT)
END
RETURN #ReturnValue
END
Then you can declare some table to store results of your stored procedure and if it is eligible, then run your code
DECLARE #FooValue int;
EXEC #FooValue = SubjectExists 'helloWorld!:)'
IF #FooValue = 1
BEGIN
DELETE FROM Subject
WHERE Subject_Name = #SubjName;
END
You need to get the return value from the stored procedure like this-
DECLARE #returnvalue INT
EXEC #returnvalue = StudentExists Bob
and then you can make your If condition.

How to pass more than one char as a variable in a stored procedure?

I've created the following stored procedure:
ALTER PROCEDURE [dbo].[CountInJunction]
#Mod as nvarchar(10),
#Junction as nvarchar(10),
#PJ as nvarchar(10),
**#case as varchar(10)**,
#Date as varchar(20)
as
begin
declare #result as int
select #result = count(distinct CONCAT ([UCID],[CALLSEGMENT]))
from IVR_LINES
where MODULE = #Mod and DATE = #date
and EVENT_NAME = #Junction and **EVENT_VALUE in (#case)**
insert into [dbo].[MainJuncTable] values(#Mod,#PJ,#Junction,#case,#result,null,null,#date)
return #result
end
I would like to pass ('0','5') as #case.
for some reason, I get 0 as a result, which is not correct. Its seems that the SP doesn't interpret ('0','5') correctly.
I've been trying multiple combinations such as:
'0','5'
'0'+','+5''
'0,5'
etc..
nothing works.
Is there any way I can pass these chars correctly?
Thanks.
Send the values as a single string like ('0,5')
Then in where condition u need to split and select the values like,
where EVENT_VALUE in (select val from Split(#case,','))
Split is user defined function,you need to create before using it.
CREATE FUNCTION [dbo].[Split]
(
#delimited nvarchar(max),
#delimiter nvarchar(100)
) RETURNS #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id int identity(1,1), -- I use this column for numbering splitted parts
val nvarchar(max)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(max)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
GO
In every case, use this as your parameter value: '0,5'
But how to use it depends on the version of sql server you're using.
If you've got 2016, there's STRING_SPLIT. https://msdn.microsoft.com/en-us/library/mt684588.aspx
If you don't have it, you can create a function. See related stackoverflow posts: How to split a comma-separated value to columns
Or if you want rows: SQL query to split column data into rows
(See the higher rated recommendations in both of those.)

Create IN subquery from a VARCHAR parameter with comma separated values [duplicate]

This question already has answers here:
T-SQL stored procedure that accepts multiple Id values
(5 answers)
Closed 8 years ago.
I have a stored procedure that I need to pass a comma-delimited variable to.
For example, if a certain condition exists, I would pass a list of country codes like this:
AU,RA,PK
The number of items can vary.
In the stored procedure, I need to use those items in an IN clause such as follows:
WHERE CountryCode IN (#ExcludeCountries)
Is there any way to do this? I can massage the country codes going in to something like N'AU', N'RA', N'PK' if need be.
Thanks.
WHERE CountryCode IN (SELECT *
FROM [dbo].[ufn_CSVToTable](#ExcludeCountries,',')
This is how you can create [dbo].[ufn_CSVToTable]:
How to convert comma separated NVARCHAR to table records in SQL Server 2005?
In practice I've seen this problem resolved using the function below. This function splits the string and return a table containing the values specified. Then use it like this...
...CountryCode IN (SELECT * FROM dbo.SplitString(#ExcludeCountries ,','))
Notice that in this example the comma (',') is the delimiter. I've used this method for years and I haven't encounter any performance problems.
CREATE FUNCTION [dbo].[SplitString]
(
#SplitStr nvarchar(MAX),
#SplitChar nvarchar(5)
)
RETURNS #RtnValue table
(
Data nvarchar(MAX)
)
AS
BEGIN
Declare #Count int
Set #Count = 1
While (Charindex(#SplitChar,#SplitStr)>0)
Begin
Insert Into #RtnValue (Data)
Select
Data = ltrim(rtrim(Substring(#SplitStr,1,Charindex(#SplitChar,#SplitStr)-1)))
Set #SplitStr = Substring(#SplitStr,Charindex(#SplitChar,#SplitStr)+1,len(#SplitStr))
Set #Count = #Count + 1
End
Insert Into #RtnValue (Data)
Select Data = ltrim(rtrim(#SplitStr))
Return
END
There are two ways to do that:
Using the #ExcludeCountries parameter build table variable table that is latter join to your query
Build a dynamic SQL statement like this
... + 'WHERE CountryCode IN' (' + #ExcludeCountries + ')' ...
and then use sp_executesql to exec the statement

SQL variable to hold list of integers

I'm trying to debug someone else's SQL reports and have placed the underlying reports query into a query windows of SQL 2012.
One of the parameters the report asks for is a list of integers. This is achieved on the report through a multi-select drop down box. The report's underlying query uses this integer list in the where clause e.g.
select *
from TabA
where TabA.ID in (#listOfIDs)
I don't want to modify the query I'm debugging but I can't figure out how to create a variable on the SQL Server that can hold this type of data to test it.
e.g.
declare #listOfIDs int
set listOfIDs = 1,2,3,4
There is no datatype that can hold a list of integers, so how can I run the report query on my SQL Server with the same values as the report?
Table variable
declare #listOfIDs table (id int);
insert #listOfIDs(id) values(1),(2),(3);
select *
from TabA
where TabA.ID in (select id from #listOfIDs)
or
declare #listOfIDs varchar(1000);
SET #listOfIDs = ',1,2,3,'; --in this solution need put coma on begin and end
select *
from TabA
where charindex(',' + CAST(TabA.ID as nvarchar(20)) + ',', #listOfIDs) > 0
Assuming the variable is something akin to:
CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)
And the Stored Procedure is using it in this form:
ALTER Procedure [dbo].[GetFooByIds]
#Ids [IntList] ReadOnly
As
You can create the IntList and call the procedure like so:
Declare #IDs IntList;
Insert Into #IDs Select Id From dbo.{TableThatHasIds}
Where Id In (111, 222, 333, 444)
Exec [dbo].[GetFooByIds] #IDs
Or if you are providing the IntList yourself
DECLARE #listOfIDs dbo.IntList
INSERT INTO #listofIDs VALUES (1),(35),(118);
You are right, there is no datatype in SQL-Server which can hold a list of integers. But what you can do is store a list of integers as a string.
DECLARE #listOfIDs varchar(8000);
SET #listOfIDs = '1,2,3,4';
You can then split the string into separate integer values and put them into a table. Your procedure might already do this.
You can also use a dynamic query to achieve the same outcome:
DECLARE #SQL nvarchar(8000);
SET #SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + #listOfIDs + ')';
EXECUTE (#SQL);
Note: I haven't done any sanitation on this query, please be aware that it's vulnerable to SQL injection. Clean as required.
For SQL Server 2016+ and Azure SQL Database, the STRING_SPLIT function was added that would be a perfect solution for this problem. Here is the documentation:
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
Here is an example:
/*List of ids in a comma delimited string
Note: the ') WAITFOR DELAY ''00:00:02''' is a way to verify that your script
doesn't allow for SQL injection*/
DECLARE #listOfIds VARCHAR(MAX) = '1,3,a,10.1,) WAITFOR DELAY ''00:00:02''';
--Make sure the temp table was dropped before trying to create it
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;
--Create example reference table
CREATE TABLE #MyTable
([Id] INT NOT NULL);
--Populate the reference table
DECLARE #i INT = 1;
WHILE(#i <= 10)
BEGIN
INSERT INTO #MyTable
SELECT #i;
SET #i = #i + 1;
END
/*Find all the values
Note: I silently ignore the values that are not integers*/
SELECT t.[Id]
FROM #MyTable as t
INNER JOIN
(SELECT value as [Id]
FROM STRING_SPLIT(#listOfIds, ',')
WHERE ISNUMERIC(value) = 1 /*Make sure it is numeric*/
AND ROUND(value,0) = value /*Make sure it is an integer*/) as ids
ON t.[Id] = ids.[Id];
--Clean-up
DROP TABLE #MyTable;
The result of the query is 1,3
In the end i came to the conclusion that without modifying how the query works i could not store the values in variables. I used SQL profiler to catch the values and then hard coded them into the query to see how it worked. There were 18 of these integer arrays and some had over 30 elements in them.
I think that there is a need for MS/SQL to introduce some aditional datatypes into the language. Arrays are quite common and i don't see why you couldn't use them in a stored proc.
There is a new function in SQL called string_split if you are using list of string.
Ref Link STRING_SPLIT (Transact-SQL)
DECLARE #tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(#tags, ',')
WHERE RTRIM(value) <> '';
you can pass this query with in as follows:
SELECT *
FROM [dbo].[yourTable]
WHERE (strval IN (SELECT value FROM STRING_SPLIT(#tags, ',') WHERE RTRIM(value) <> ''))
I use this :
1-Declare a temp table variable in the script your building:
DECLARE #ShiftPeriodList TABLE(id INT NOT NULL);
2-Allocate to temp table:
IF (SOME CONDITION)
BEGIN
INSERT INTO #ShiftPeriodList SELECT ShiftId FROM [hr].[tbl_WorkShift]
END
IF (SOME CONDITION2)
BEGIN
INSERT INTO #ShiftPeriodList
SELECT ws.ShiftId
FROM [hr].[tbl_WorkShift] ws
WHERE ws.WorkShift = 'Weekend(VSD)' OR ws.WorkShift = 'Weekend(SDL)'
END
3-Reference the table when you need it in a WHERE statement :
INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM #ShiftPeriodList)
You can't do it like this, but you can execute the entire query storing it in a variable.
For example:
DECLARE #listOfIDs NVARCHAR(MAX) =
'1,2,3'
DECLARE #query NVARCHAR(MAX) =
'Select *
From TabA
Where TabA.ID in (' + #listOfIDs + ')'
Exec (#query)

Duplicate Auto Numbers generated in SQL Server

Be gentle, I'm a SQL newbie. I have a table named autonumber_settings like this:
Prefix | AutoNumber
SO | 112320
CA | 3542
A whenever a new sales line is created, a stored procedure is called that reads the current autonumber value from the 'SO' row, then increments the number, updates that same row, and return the number back from the stored procedure. The stored procedure is below:
ALTER PROCEDURE [dbo].[GetAutoNumber]
(
#type nvarchar(50) ,
#out nvarchar(50) = '' OUTPUT
)
as
set nocount on
declare #currentvalue nvarchar(50)
declare #prefix nvarchar(10)
if exists (select * from autonumber_settings where lower(autonumber_type) = lower(#type))
begin
select #prefix = isnull(autonumber_prefix,''),#currentvalue=autonumber_currentvalue
from autonumber_settings
where lower(autonumber_type) = lower(#type)
set #currentvalue = #currentvalue + 1
update dbo.autonumber_settings set autonumber_currentvalue = #currentvalue where lower(autonumber_type) = lower(#type)
set #out = cast(#prefix as nvarchar(10)) + cast(#currentvalue as nvarchar(50))
select #out as value
end
else
select '' as value
Now, there is another procedure that accesses the same table that duplicates orders, copying both the header and the lines. On occasion, the duplication results in duplicate line numbers. Here is a piece of that procedure:
BEGIN TRAN
IF exists
(
SELECT *
FROM autonumber_settings
WHERE autonumber_type = 'SalesOrderDetail'
)
BEGIN
SELECT
#prefix = ISNULL(autonumber_prefix,'')
,#current_value=CAST (autonumber_currentvalue AS INTEGER)
FROM autonumber_settings
WHERE autonumber_type = 'SalesOrderDetail'
SET #new_auto_number = #current_value + #number_of_lines
UPDATE dbo.autonumber_settings
SET autonumber_currentvalue = #new_auto_number
WHERE autonumber_type = 'SalesOrderDetail'
END
COMMIT TRAN
Any ideas on why the two procedures don't seem to play well together, occasionally giving the same line numbers created from scratch as lines created by duplication.
This is a race condition or your autonumber assignment. Two executions have the potential to read out the same value before a new one is written back to the database.
The best way to fix this is to use an identity column and let SQL server handle the autonumber assignments.
Barring that you could use sp_getapplock to serialize your access to autonumber_settings.
You could use repeatable read on the selects. That will lock the row and block the other procedure's select until you update the value and commit.
Insert WITH (REPEATABLEREAD,ROWLOCK) after the from clause for each select.

Resources