Select IN using varchar string with comma delimited values - sql-server

I am trying to search several tables for a list of phones.
The problem is converting the single string into a valid comma delimited string to use in conjunction with the IN clause.
I tried using replace to fix the problem.
DECLARE #PhoneNumber VARCHAR(3000)
SET #PhoneNumber = '6725556666,2124444444'
SET #PhoneNumber = '''' + #PhoneNumber + ''''
SELECT #PhoneNumber
'6725556666','2124444444'
Finally the sample SQL does not recognize the string as expected:
SELECT Provider
,PhoneNumber
,ChangeType
,ChangeDate
FROM dbo.PhoneLog
WHERE PhoneNumber IN (#PhoneNumber)

There are several ways to handle this. One option is to use dynamic sql and inject your phone number string into a variable containing the statement and then executing that like this:
DECLARE #PhoneNumber VARCHAR(3000)
SET #PhoneNumber = '6725556666,2124444444'
DECLARE #SQL NVARCHAR(max)
SET #SQL = N'
SELECT Provider, PhoneNumber, ChangeType, ChangeDate
FROM dbo.PhoneLog
WHERE PhoneNumber IN (' + #PhoneNumber + ')'
EXEC sp_executesql #SQL
Please note that this approach can be vulnerable to SQL injection attacks, for instance feeding a string like
SET #PhoneNumber = '1);truncate table phonelog;--'
would effectively empty the table. So using a dynamic SQL approach like above should only be an option if it's certain that the string fed that in injected is sanitized and safe (or maybe it should never be used).
Another, possibly better, option is to use a user defined function to split the phonenumber variable and use that like this:
SELECT Provider, PhoneNumber, ChangeType, ChangeDate
FROM dbo.PhoneLog
WHERE PhoneNumber IN (
SELECT splitdata FROM dbo.fnSplitString(#PhoneNumber,',')
-- you could add a check here that the data returned from the function
-- is indeed numeric and valid
-- WHERE ISNUMERIC(splitdata) = 1
)
Here's the function used in the example:
CREATE FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
I did not write the function, I think I got it somewhere on the internet...

Related

SQL Server stored procedures dynamic SQL with multiple different where clause

Input CASE1=wen #type is NULL-> WHERE c.Date_Redeemed BETWEEN #Start AND #End-- this should execute
If CASE2=wen #start,#end is NULL->WHERE c.Type=#type-- this should execute
CASE 3=wen #value is null->WHERE c.Date_Redeemed BETWEEN #Start AND #End AND c.Type=#type this should execute
CASE4=wen #marketclass is NULL->WHERE c.Date_Redeemed BETWEEN #Start AND #End AND c.Type=#type AND c.ordervalue BETWEEN #price1 AND #price2
VAR=#type,#start,#price1,#marketclass if eithr of var is NULL dynamicaly other input where condition has to execute like diff combination of input comes
WHERE
(c.Type = #type AND o.Date_of_Purchase BETWEEN #start AND #end) OR
(#start IS NULL AND c.Type = #type) OR
(#type IS NULL and o.Date_of_Purchase BETWEEN #start AND #end) OR
(#start IS NULL AND #type IS NULL)
if both are provided, only rows where type and date match are returned,
if type is not provided, date is used,
if date not provided type is used and
if nothing provided all rows return. To make no rows return, remove the last predicate
Code:
CREATE PROCEDURE dbo.sample
#start DATE, #end DATE,
#type VARCHAR(5),
#price1 MONEY, #price2 MONEY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(MAX)
SET #SQL= 'SELECT DISTINCT o.O_Id,o.Sale_Price,o.Order_Line_Id,Private_Band,c.Date_of_Purchase,c.Date_Redeemed,c.Credit_Memo,c.Credit_Memo_Created_Date,c.Credit_Memo_Approved_Date,
c.Type,c.Points_Issued,o.Date_of_Purchase FROM Order_Details o ,Transaction_Historys c WHERE -1='-1''
IF #type IS NOT NULL AND #type <> 0
SET #SQL = #SQL+ 'c.Type = #type'
ELSE
IF #start IS NOT NULL AND #start <> 0
SET #SQL = #SQL+ 'c.Date_redeemed BETWEEN #start AND #end'
EXECUTE dbo.sample #type='Earn',#start='2010-02-10',#end='2020-04-11'
END
Having looked through this, I'm actually kind of reluctant to post this answer, because there's so many things wrong with this:
prone to SQL injection;
using antiquated JOIN syntax;
clunky logic.
The simple answer is that sometimes just because you CAN do something in SQL doesn't mean you actually SHOULD.
Anyway, I think is closer to what you wanted?
CREATE PROCEDURE dbo.[sample] (
#start DATE,
#end DATE,
#type VARCHAR(5),
#price1 NUMERIC(9,2),
#price2 NUMERIC(9,2))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql VARCHAR(1024);
SELECT #sql = N'
SELECT DISTINCT
o.O_Id,
o.Sale_Price,
o.Order_Line_Id,
Private_Band, --alias?
c.Date_of_Purchase,
c.Date_Redeemed,
c.Credit_Memo,
c.Credit_Memo_Created_Date,
c.Credit_Memo_Approved_Date,
c.Type,
c.Points_Issued,
o.Date_of_Purchase
FROM
Order_Details o,
Transaction_Historys c'; --Join condition?!
DECLARE #constraint VARCHAR(1024) = '';
IF #type IS NOT NULL AND #type != 0
BEGIN
SELECT #constraint = CONCAT(' WHERE c.Type = ''', #type, '''');
END;
IF #start IS NOT NULL AND #end IS NOT NULL
BEGIN
SELECT #constraint = CONCAT(#constraint,
CASE WHEN #constraint = '' THEN ' WHERE ' ELSE ' AND ' END,
' c.Date_redeemed BETWEEN ''',
CONVERT(VARCHAR(12), #start, 112),
''' AND ''',
CONVERT(VARCHAR(12), #end, 112)),
'''';
END;
SELECT #sql = CONCAT(#sql, #constraint);
EXEC sp_executesql #sql;
END;
GO
EXEC dbo.[sample] #type = 'Earn', #start = '20100210', #end = '20200411', #price1 = NULL, #price2 = NULL;
I made quite a few changes here:
I couldn't bring myself to use a MONEY type, so change it back if you really want it;
I added some comments to your script ;)
I sort of fixed the actual issue, that you need to cope with one or two or even no constraints being passed in.

DynamicSQL Split function to add criteria from comma delimited string not working

I'm trying to use a split function to add a range of values to a query. I'd also like to 'OR' the values together but first things first. I'm following an example I have found in a much larger query that uses the split function, so I've made a small query to try to figure out how it works. So far though, all I get is the error *"The name 'Select * from Country As sp WHERE (sp.CountryID in (SELECT [Value] FROM dbo.Split('2,22,', ',')))' is not a valid identifier."* I'm knew to DynamicSQL and I'm not quite sure how this split function is supposed to work.
DECLARE #Countries varchar(MAX);
DECLARE #FiltersOn bit;
DECLARE #Country int;
DECLARE #Query varchar(MAX);
Set #FiltersOn = 0;
Set #Query = 'Select * from Country As sp ';
Set #Countries ='2,22,'
IF ( #Countries IS NOT NULL )
BEGIN
IF ( #FiltersOn = 1 )
BEGIN
SET #Query = #Query + ' AND '
END
ELSE
BEGIN
SET #Query = #Query + ' WHERE '
SET #FiltersOn = 1
END
SET #Query = #Query
+ '(sp.CountryID in (SELECT [Value] FROM dbo.Split('''
+ #Countries + ''', '','')))'
END
EXEC #Query
This is the definition of the Country table:
CREATE TABLE [dbo].[Country](
[CountryID] [int] IDENTITY(1,1) NOT NULL,
[AgentID] [int] NULL,
[Name] [varchar](50) NULL,
[CountryLookupID] [int] NOT NULL
and this is the split function code:
CREATE FUNCTION [dbo].[Split]
(
#String varchar(8000),
#Delimiter varchar(10)
)
RETURNS #ValueTable table ([Value] varchar(255))
BEGIN
DECLARE #NextString varchar(4000)
DECLARE #Pos int
DECLARE #NextPos int
DECLARE #DelimiterCheck varchar(1)
-- initialise
SET #NextString = ''
SET #DelimiterCheck = RIGHT(#String, 1)
-- add trailing delimiter
IF (#DelimiterCheck <> #Delimiter)
SET #String = #String + #Delimiter
-- find position of first delimiter
SET #Pos = CHARINDEX(#Delimiter, #String)
SET #NextPos = 1
-- loop while there is a delimiter in the string
WHILE (#Pos <> 0)
BEGIN
SET #NextString = SUBSTRING(#String, 1, #Pos - 1)
INSERT INTO #ValueTable ([Value]) VALUES (#NextString)
SET #String = SUBSTRING(#String, #Pos + 1, LEN(#String))
SET #NextPos = #Pos
SET #Pos = CHARINDEX(#Delimiter, #String)
END
RETURN
END
You are not writing EXEC statement correctly.
use EXEC (#Query) instead of EXEC #Query
This is not a direct answer to your question, but something I can recomend reading through as I've seend these solutions create problems in systems before.
I would suggest not using the solution using the WHILE loop. While loops constantly create performance problems as SQL Server doesn't have the same ability to optimize loops as for example C# engines does.
Not sure why you are using dynamic SQL either. If you want to make sure that there is no SQL injection please use sp_executesql instead as it checks for harmful code but i would avoid using dynamic sql except when we don't know the structure of our underlying data (hence need for dynamic sql).
Just wrote a function for doing a string split using cte to keep perfomance up.
CREATE SCHEMA Util;
GO
CREATE FUNCTION Util.String_Split (
#Text varchar(MAX),
#SplitChar char
)
RETURNS TABLE
AS
RETURN(
WITH cte AS
(
SELECT
1 AS [RowNumber],
X.Text,
X.RemainingText
FROM
(
SELECT
SUBSTRING(#Text, 1, CHARINDEX(';', #Text) - 1) AS [Text],
SUBSTRING(#Text, CHARINDEX(';', #Text) + 1, LEN(#Text) - CHARINDEX(';', #Text)) AS [RemainingText]
) X
UNION ALL
SELECT
cte.RowNumber + 1,
X.Text,
X.RemainingText
FROM
cte
CROSS APPLY (
SELECT
SUBSTRING(cte.RemainingText, 1, ISNULL(NULLIF(CHARINDEX(';', cte.RemainingText) - 1, -1), LEN(cte.RemainingText))) AS [Text],
CASE
WHEN CHARINDEX(';', cte.RemainingText) = 0 THEN
''
ELSE
SUBSTRING(cte.RemainingText, CHARINDEX(';', cte.RemainingText) + 1, LEN(cte.RemainingText))
END AS [RemainingText]
) X
WHERE
X.Text ''
)
SELECT
cte.Text
FROM
cte
WHERE
cte.Text IS NOT NULL
);
You could then call make your query without the dynamic part using this call:
SELECT
*
FROM
Country sp
WHERE
#Countries IS NULL
OR
sp.CountryId IN (
SELECT * FROM Util.String_Split(#Countries, ',')
)

How to get values enclosed within special characters in a long string

I have a long string containing three special symbols ' # and ? and some text are enclosed inside these symbols.
for ex. "#sa#32#ddd#?222?#ds#asa#hhh#?ddsds?dsdsd?cccc?'cxcx'?ccxc?cxc?'cxcx'"
I want the values sa 32 ddd 222 ds etc.. and insert these values in a table.
Next step is to insert these values inside different column of same row in a table.
How can I achieve that.
The following function allows you to split the given string on the basis of a single delimiter.
CREATE FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
You can use this function iteratively for each of the individual delimiters, over each individual string obtained from the first run and the final output should provide you the requisite values.
Try this way. The complete answer It depends od more information
SET QUOTED_IDENTIFIER OFF
declare #test varchar(100)
set #test ="#sa#32#ddd#?222?#ds#asa#hhh#?ddsds?dsdsd?cccc?'cxcx'?ccxc?cxc?'cxcx'"
print replace(replace(#test,'#',' '),'?',' ')

Convert comma delimited string to table or array in sql server 2008 without using dbo.split

How to convert comma delimited string to table or array in sql server 2008 without using dbo.split function because the system doesn’t support this function?
Eg of string: ’12,14,15’
Change this to
*Table column*
12
14
15
Or array=[12,14,15]
I would like to insert comma separated string values into a table ultimately.
Thanks
dbo.split is probably user defined function, so you need to define it. Otherwise you can use XML + CROSS APPLY:
Demo
DECLARE #string NVARCHAR(100) = '12,14,15'
;WITH cte AS
(
SELECT
CAST('<XMLRoot><RowData>' + REPLACE(t.val,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
FROM (SELECT #string) AS t(val)
)
SELECT
m.n.value('.[1]','varchar(8000)')
FROM cte
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
If you don't want to use split, you can create your own custom function to achieve this.
Please follow below stack question for same.
How to convert comma separated NVARCHAR to table records in SQL Server 2005?
Below link covers all possible way to achieve this.
http://blogs.msdn.com/b/amitjet/archive/2009/12/11/sql-server-comma-separated-string-to-table.aspx
ALTER FUNCTION [dbo].[fnSplitString] (
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS
#output TABLE(splitdata NVARCHAR(MAX) )
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1
BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END

Stored Procedure parameter at WHERE IN clause

I have a stored procedure and it looks something like this:
SELECT * FROM empTable
WHERE empCode IN (#employeeCode)
The #employeeCode will accept an input from another application where I do NOT have the control of that application. It will give me a string something like this: 'M06', 'R02', 'B19', 'C10'
When I execute the procedure, it returns me 0 row. However, when I run the query manually in the SQL Server using the following query:
SELECT * FROM empTable
WHERE empCode IN ('M06', 'R02', 'B19', 'C10')
It returns me 15 rows.
I'm suspecting that when the employeeCode accepting the input, it assumes the entire 'M06', 'R02', 'B19', 'C10' as ONE string. Can anybody confirm this? If this is true, how am I going to fix it?
Edited: I'm using SQL Server 2000
Here's your savior! (5 years later)
Use a SQL Function!
What the following code do?
The function fn_split_string_to_column takes the list using a common separator and returns a table
CREATE FUNCTION [dbo].[fn_split_string_to_column] (
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #out_put TABLE (
[column_id] INT IDENTITY(1, 1) NOT NULL,
[value] NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #value NVARCHAR(MAX),
#pos INT = 0,
#len INT = 0
SET #string = CASE
WHEN RIGHT(#string, 1) != #delimiter
THEN #string + #delimiter
ELSE #string
END
WHILE CHARINDEX(#delimiter, #string, #pos + 1) > 0
BEGIN
SET #len = CHARINDEX(#delimiter, #string, #pos + 1) - #pos
SET #value = SUBSTRING(#string, #pos, #len)
INSERT INTO #out_put ([value])
SELECT LTRIM(RTRIM(#value)) AS [column]
SET #pos = CHARINDEX(#delimiter, #string, #pos + #len) + 1
END
RETURN
END
SELECT * FROM empTable WHERE empCode IN (#employeeCode) -- this wont work!
SELECT * FROM empTable WHERE empCode IN (
select value from fn_split_string_to_column(#employeeCode,',') -- this will work!
)
Examples!
Using comma as separator
declare #txt varchar(100) = 'a,b,c, d, e'
select * from fn_split_string_to_column(#txt,',')
Query results:
Using space as separator
declare #txt varchar(100) = 'Did you hear about the guy who lost his left arm and left leg in an accident? He’s all right now.'
select * from fn_split_string_to_column(#txt,' ')
Query results:
Source
The function above is not mine. I borrowed from the answer of the following question:How to split a comma-separated value to columns
Go there and give him an upvote!
(Actually you should go there and read the comments about performance issues you should know about)
Or just use dynamic SQL. It would look something like this:
EXEC('SELECT * FROM empTable WHERE empCode IN (' + #employeeCode + ')')

Resources