VARCHAR range as variable? - sql

I have a stored procedure that needs to be filtered by a select box that could have a value between 0 to 3. This value corresponds to table where each int value could have two or more VARCHAR values associated with it. For example 0 could be assigned to 'A' 'B' or 'C' while 1 could be 'D' or 'E'
I have attempted to store these options as a variable #m which is set like this:
DECLARE #m varchar(50) =
CASE
WHEN #mid = 0
THEN N'''A'',''B'',''C'',''D'',''E'''
WHEN #mid = 1
THEN N'''A'',''B'''
WHEN #mid = 2
THEN N'(''C'',''D'')'
ELSE N'''E'''
END
Which returns the expected value if I return it on it's own, with the ' marks and , separators in the correct places. But when I try to use it to select the correct records from my table, I get nothing.
select #m --Returns 'C','D','E'
select * from table where mValue in (#m) -- Returns nothing
select * from table where mValue in ('C','D','E') -- Returns all expected rows
And it doesn't make any difference if I have the brackets in the #m variable or not, then second select won't return any rows.

As Gordon mentioned, you will have to create a function for splitting your string. I use below logic (if it's always delimited by ,)
CREATE FUNCTION [dbo].[fn_split_string] ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
Then you can simply use this function in your query.
select * from table where mValue in (select * from fn_split_string (#m))

One method is to split #m. You can find various "split" functions on the web (the functionality was added in SQL Server 2016).
The idea is then to do something like:
with m(item) as (
select *
from dbo.split(#m, ',') s
)
select *
from table
where mValue in (select item from m) ;

Related

I want my function to return a table that contains the numbers inside the #

I am getting the following error :
Msg 208, Level 16, State 6, fnToken Procedure, Line 24
Invalid object name 'dbo.fnToken'.
I tried returning all the numbers ..
select dbo.fnToken('#254#251451#')
ALTER FUNCTION dbo.fnToken
(
#Token varchar(100)
)
RETURNS #Listenum TABLE(num varchar(50))
AS
begin
declare #compt int
declare #num varchar(50)
set #compt = 1
while SUBSTRING(#token,#compt,1)<>''
begin
if SUBSTRING(#Token,#compt+1,#compt)<>'#'
begin
set #num= #num+SUBSTRING(#Token,#compt+1,1)
set #compt=#compt+1
end
else
begin
Insert into #Listenum(num) values(#num)
end
end
Return (SELECT num FROM #Listenum)
ENd
This is the expected output :
254
251451
You can try:
SELECT value
FROM STRING_SPLIT('#254#251451#', '#')
WHERE TRIM(value) <> '';
Depending on your version of SQL-Server its a built in function. If your on an older version have a look at string split functions on here. There are plenty of options.
You can't reference a Table-Value Function like a Scalar Function. But this is a really bad way of splitting values out of a delimited value as it's iterative. Use an XML Splitter, DelimitedSplit8K_LEAD or STRING_SPLIT (if you're on SQL Server 2016+).
If you're using STRING_SPLIT the correct syntax would be:
SELECT SS.[value] AS num
FROM STRING_SPLIT(''#254#251451#','#') SS
WHERE SS.[value] != '';
The same syntax would be true for DelimitedSplit8K_LEAD, if you're not on SQL Server 2016+.
Edit: Op is using an old (and about to be completely unsupported) version of SQL Server, so they will need to use DelimitedSplit8k.
Here is a solution. Hope to help, my friend :))
I added more #separator param, you can remove it by fixing "#" value.
ALTER FUNCTION dbo.fnToken
(
#stringToSplit VARCHAR(MAX), #separator nchar(1)
)
RETURNS #Listenum TABLE(num varchar(50))
AS
begin
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(#separator, #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(#separator, #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #Listenum
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #Listenum
SELECT #stringToSplit
Return
ENd
SELECT num
FROM dbo.fnToken('#254#251451#', '#')
WHERE TRIM(num) <>''

splitting input string in sql and getting its value from another table

I have a table hobby and another table hobbyValue.
Hobby Table
===========
hobbies (It is a list of hobbies which user has inserted using checkbox. It gets stores hobby as Ids with a delimiter ^ for e.g hobbyId1^hobbyId2^hobbyId3^)
Hobby Value Table (it has two columns id, Value)
============
hobbyId1 | Football
hobbyId2 | baseball
hobbyId3 | chess
I am restricted to use this kind of table format because it is part of a big application. I am not sure how can I write a sql function where input string is hobbies from Hobby table and out put will be its values.
for e.g.
============================
string inputHobbies = hobbies from Hobby table , hobbyId1^hobbyId2^hobbyId3^
outputValues = input my_sql_function(inputHobbies)
outputValues should be football,baseball,chess
I dont even know how should I start for to get this.
Firstly, you will need some string split capability. If you are on Sql Server 2016+, there should be built in STRING_SPLIT function (https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql).
If not, you can use one below, which I've got some time ago from another SO question.
CREATE FUNCTION fnc_splitstring ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX('^', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX('^', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
Then you can split your hobby ids and join them onto the Hobby table, get the actual name and STUFF the results into a single result row again:
CREATE FUNCTION fnc_getHobbies (#listOfHobbyIds NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #hobbies NVARCHAR(MAX)
SELECT #hobbies = STUFF(( SELECT ',' + hobby
FROM dbo.fnc_splitstring(#listOfHobbyIds) hb
INNER JOIN hobby h on h.id = hb.name
FOR XML PATH('')
), 1, 1, '')
RETURN #hobbies
END
End usage like below:
SELECT dbo.fnc_getHobbies(hobbyids)
FROM hobbies
Lastly, here is the SQL Fiddle for this: http://sqlfiddle.com/#!18/44cd9
CREATE FUNCTION [dbo].[SplitForDelimiter] -- a table-valued function
(
#delimiter VARCHAR(10),
#input VARCHAR(1000)
)
RETURNS #tempTable TABLE(
data VARCHAR(100)
)
BEGIN
DECLARE #tempstr VARCHAR(1000)
SET #tempstr = #input
WHILE(charindex(#delimiter,#tempstr,0) > 0)
BEGIN
DECLARE #t VARCHAR(100)
SET #t = Substring(#tempstr,0,(charindex(#delimiter,#tempstr,0)))
INSERT into #tempTable (data) VALUES (#t)
SET #tempstr = Substring(#tempstr,charindex(#delimiter,#tempstr,0)+1,Len(#tempstr))
if(charindex(#delimiter,#tempstr,0) <=0)
BEGIN
INSERT into #tempTable (data) VALUES (#tempstr)
END
END
Then
SELECT Value from HobbyValue HV
WHERE ID IN (select * from SplitForDelimiter('^','hobbyId1^hobbyId2^hobbyId3^'))

SQL Stored Procedure LIKE

This is a simple question and I can't seem to think of a solution.
I have this defined in my stored procedure:
#communityDesc varchar(255) = NULL
#communityDesc is "aaa,bbb,ccc"
and in my actual query I am trying to use IN
WHERE AREA IN (#communityDesc)
but this will not work because my commas are inside the string instead of like this "aaa", "bbb", "ccc"
So my question is, is there anything I can do to #communityDesc so it will work with my IN statement, like reformat the string?
This article could help you by your problem:
http://sqlperformance.com/2012/07/t-sql-queries/split-strings
In this article Aaron Bertrand is writing about your problem.
It's really long and very detailed.
One Way would be this:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
With this function you only call:
WHERE AREA IN (SELECT Item FROM dbo.SplitStrings_XML(#communityDesc, N','))
Hope this could help you.
The simplest way to use this variable is:
SELECT *
FROM something
WHERE ',' + #communityDesc + ',' Like '%,' + AREA + ',%'
this is for tsql, for oracle use || to concatenate strings
In only works with sets of values, not with characters in a string. To answer your question technically, the only way you could do this is to create a set of values representing the three values 'aaa', 'bbb' & 'ccc' and then put those three values into a table (a Temp Table or table variable (in SQL Server), and then perform IN against that set of values (against the table:
declare #Vals table (value varchar(20))
insert #vals(Value) Values('aaa')
insert #vals(Value) Values('bbb')
insert #vals(Value) Values('ccc')
select * from SomeOtherTable
Where SomeColumn IN (Select value from #vals)
To create the set you would need to create an empty temp table or table variable to hold this set of values, parse the comma delimited string into individual values, and enter those individual values into the temp table or table variable.
although you don't say, if you are using SQL Server, the following is a SQL Server User Defined function (UDF) that will parse a delimited string and return a table with one row for each delimted value:
if you create the UDF, then you would use it as follows:
select * from SomeOtherTable
Where SomeColumn IN
(Select sVal from
dbo.ParseSTring(#communityDesc, ','))
/****** Object: UserDefinedFunction [dbo].[ParseString]
Script Date: 4/8/2016 1:53:00 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ParseString] (#S Text, #delim VarChar(5))
Returns #tOut Table
(ValNum Integer Identity Primary Key,
sVal VarChar(8000))
As
Begin
Declare #dLLen TinyInt -- Length of delimiter
Declare #sWin VarChar(8000)-- Will Contain Window into text string
Declare #wLen Integer -- Length of Window
Declare #wLast TinyInt -- Boolean to indicate processing Last Window
Declare #wPos Integer -- Start Position of Window within Text String
Declare #sVal VarChar(8000)-- String Data to insert into output Table
Declare #BtchSiz Integer -- Maximum Size of Window
Set #BtchSiz = 7900 -- (Reset to smaller values to test routine)
Declare #dPos Integer -- Position within Window of next Delimiter
Declare #Strt Integer -- Start Position of each data value in Window
-- --------------------------------------------------------------
-- ---------------------------
If #delim is Null Set #delim = '|'
If DataLength(#S) = 0 Or
Substring(#S, 1, #BtchSiz) = #delim Return
-- ---------------------------
Select #dLLen = Len(#delim),
#Strt = 1, #wPos = 1,
#sWin = Substring(#S, 1, #BtchSiz)
Select #wLen = Len(#sWin),
#wLast = Case When Len(#sWin) = #BtchSiz
Then 0 Else 1 End,
#dPos = CharIndex(#delim, #sWin, #Strt)
-- ----------------------------
While #Strt <= #wLen
Begin
If #dPos = 0 Begin -- No More delimiters in window
If #wLast = 1 Set #dPos = #wLen + 1
Else Begin
Set #wPos = #wPos + #Strt - 1
Set #sWin = Substring(#S, #wPos, #BtchSiz)
-- -------------------------------------
Select #wLen = Len(#sWin), #Strt = 1,
#wLast = Case When Len(#sWin) = #BtchSiz
Then 0 Else 1 End,
#dPos = CharIndex(#delim, #sWin, 1)
If #dPos = 0 Set #dPos = #wLen + 1
End
End
-- -------------------------------
Set #sVal = LTrim(Substring(#sWin, #Strt, #dPos - #Strt))
Insert #tOut (sVal) Values (#sVal)
-- -------------------------------
-- Move #Strt to char after last delimiter
Set #Strt = #dPos + #dLLen
Set #dPos = CharIndex(#delim, #sWin, #Strt)
End
Return
End
at first you most create a function to split string some thing like this code
CREATE FUNCTION dbo.splitstring ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
then you can use this function is your query like this
WHERE AREA IN (dbo.splitstring(#communityDesc))
You can do this by splitting your string using a split function provided here. The function returns a table having a single column which holds your tokens (i.e. 'aaa', 'bbb' ...).
Your query should look like this:
-- get the splits
SELECT Name INTO #someTemp
FROM dbo.splitstring(#communityDesc)
-- get data where area in within description
SELECT 1
FROM yourTable T
WHERE EXISTS (SELECT 1 FROM #someTemp tmp WHERE T.Area = tmp.Name)
A different approach is to use CHARINDEX(). However, using a function in a WHERE clause will slow down the performance.
WHERE CHARINDEX(','+area+',',','+#CommunityDec+',')> 0
If you area field is always 3 letters, you can simplify this.
WHERE CHARINDEX(area,#CommunityDec)> 0
This is a quick solution, but also a stop gap. A better solution is to change the string lookup approach to build a table with one row per search criteria and using a JOIN or sub query.
You can simply split this csv using XML and use this to filter in your query. No need to use a User defined function or #Table_Valiable or #Temp_Table here.
DECLARE #xml as xml,#communityDesc varchar(255) = 'aaa,bbb,ccc'
SET #xml = cast(('<X>'+replace(#communityDesc,',' ,'</X><X>')+'</X>') as xml)
SELECT * FROM TABLE1
WHERE AREA IN (
SELECT N.value('.', 'varchar(10)') as value FROM #xml.nodes('X') as T(N)
)
If you required this split values in further process, then you can Insert this to a #table_Variable or #Temp_Table and use them.

convert string into int SQL Server

This is the scenario:
My app will have the following:
A listbox (The checkbox property enabled) that will display a list of Something.
The user will select from the listbox (multiselect) by using the checkbox.
I will loop into All the checked items and store the ID's into an array. I will store the ID's into something like this separating the ID with a comma (1,2,3,4) and then I will use length -1 to delete the last comma.
How can I convert the string 1,2,3,4 into an integer type of data if my stored procedure is like this?
Select * from tblSomething Where ID in (1,2,3,4)
You can use the following SQL function.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CommaSeparatedToString]
(
#psCSString VARCHAR(8000)
)
RETURNS #otTemp TABLE(sID VARCHAR(20))
AS
BEGIN
DECLARE #sTemp VARCHAR(50)
WHILE LEN(#psCSString) > 0
BEGIN
SET #sTemp = LEFT(#psCSString, ISNULL(NULLIF(CHARINDEX(',', #psCSString) - 1, -1),
LEN(#psCSString)))
SET #psCSString = SUBSTRING(#psCSString,ISNULL(NULLIF(CHARINDEX(',', #psCSString), 0),
LEN(#psCSString)) + 1, LEN(#psCSString))
INSERT INTO #otTemp VALUES (#sTemp)
END
RETURN
END
And call in your stored procedure like
Select * from tblSomething
Where ID in (SELECT * FROM CommaSeparatedToString('1,2,3,4'))
You can use the
SELECT CAST(MyVarcharCol AS INT) FROM Table
SELECT CONVERT(INT, MyVarcharCol) FROM Table
refer this link
http://msdn.microsoft.com/en-us/library/ms187928.aspx
You need to create dynamic query for this
e.g you are getting list of values in #values paramter so prepare and run the dynamic query like this
DECLARE #query NVARCHAR(500)
DECLARE #values VARCHAR(200)
SET #values='1,2'
SET #query =N'Select * from tblSomething Where ID in ( ' + #values + ')'
SELECT #query
EXEC #Query
Use this function to split the value:
CREATE FUNCTION [dbo].[udfSplitCSV]
(
#String varchar (max),
#Delimiter varchar (10) = ','
)
RETURNS #ValueTable TABLE ([Row] int IDENTITY(1,1), [Value] varchar(max), [Length] int, [Duplicate] int NULL)
BEGIN
DECLARE #NextString varchar(max)
DECLARE #Pos int
DECLARE #NextPos int
IF #String IS NULL RETURN
--Initialize
SET #NextString = ''
SET #String = #String + #Delimiter
--Get position of first Comma
SET #Pos = charindex(#Delimiter,#String)
SET #NextPos = 1
--Loop while there is still a comma in the String
WHILE (#Pos <> 0)
BEGIN
SET #NextString = RTrim(LTrim(SubString(#String,1,#Pos - 1)))
INSERT INTO #ValueTable ([Value], [Length]) VALUES (#NextString, Len(#NextString))
SET #String = SubString(#String,#Pos+1,Len(#String))
SET #NextPos = #Pos
SET #Pos = CharIndex(#Delimiter,#String)
END
UPDATE #ValueTable
SET [Duplicate] = X.Duplicate
FROM #ValueTable VT
INNER JOIN (Select [Row], [Value], Row_Number() OVER (Partition By [Value] ORDER BY [Value], [Row]) as Duplicate FROM #ValueTable) X
ON X.[Row] = VT.[Row]
RETURN
END
-- Select * from dbo.udfSplitCSV('a , c b,c, a', ',')
When you are storing a bunch of IDs into the array, store with single quote.
so it will be ('1','2','3').
Then you no need to covert IDs into integer.

stored proc - executing a query with NOT IN where clause

i have a stored procedure
Create PROCEDURE abc
#sRemovePreviouslySelectedWhereClause nvarchar(max)
AS
BEGIN
SELECT *
FROM table
WHERE nId NOT IN (#sRemovePreviouslySelectedWhereClause)
END;
The parameter #sRemovePreviouslySelectedWhereClause can have values like 0,1 . But this fails with error message:
Conversion failed when converting the nvarchar value ' 0,1 ' to data type int.
Is there any other way to achieve this other than dynamic queries?
First, create a split function which splits your delimited string into a table:
CREATE FUNCTION [dbo].[Split]
(
#String varchar(max)
,#Delimiter char
)
RETURNS #Results table
(
Ordinal int
,StringValue varchar(max)
)
as
begin
set #String = isnull(#String,'')
set #Delimiter = isnull(#Delimiter,'')
declare
#TempString varchar(max) = #String
,#Ordinal int = 0
,#CharIndex int = 0
set #CharIndex = charindex(#Delimiter, #TempString)
while #CharIndex != 0 begin
set #Ordinal += 1
insert #Results values
(
#Ordinal
,substring(#TempString, 0, #CharIndex)
)
set #TempString = substring(#TempString, #CharIndex + 1, len(#TempString) - #CharIndex)
set #CharIndex = charindex(#Delimiter, #TempString)
end
if #TempString != '' begin
set #Ordinal += 1
insert #Results values
(
#Ordinal
,#TempString
)
end
return
end
Then change your where clause as follows:
select
t.*
from [yourTable] t
where t.[ID] not in (select cast([StringValue] as int) from dbo.Split(#sRemovePreviouslySelectedWhereClause,','))
Create FUNCTION [dbo].[fn_Split] (
#List nvarchar(2000), #SplitOn nvarchar(5)
)
RETURNS #RtnValue table (
Value nvarchar(100) )
AS
BEGIN
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
**********
Create PROCEDURE abc
#sRemovePreviouslySelectedWhereClause nvarchar(max)
AS
BEGIN
SELECT *
FROM Table
WHERE nId NOT IN (select * from dbo.fn_Split(#sRemovePreviouslySelectedWhereClause,','))
END;
You have to split the #sRemovePreviouslySelectedWhereClause parameter by ',' and place the resulting values in a temp table. Then your select would look like
select * from table where nId not in (select nId from #tempIds)
This approach you're trying doesn't work. But if you're on SQL Server 2008, you could make use of the new features called Table Valued Parameters.
Basically, you create a user-defined table type
CREATE TYPE dbo.nIdTable AS TABLE(nID INT)
and you can then pass in multiple values in that TVP from the outside (e.g. from ADO.NET or such):
CREATE PROCEDURE abc(#idValues dbo.nIdTable READONLY)
and use that table variable inside your stored proc:
SELECT *
FROM table
WHERE nId NOT IN (SELECT nID FROM #idValues)
You will need to use Dynamic sql for such kind of queries.
first construct the query and
SET #sql = 'select * from table
where nId not in (' + #sRemovePreviouslySelectedWhereClause+ ')'
then use EXEC(#sql) to run the query.