formatting a string param to be the IN statement - sql-server

I have a bit of code:
declare #GroupNames nvarchar(1024)
EXEC Utility.dbo.Get_ADGroups_ForUser 'rwm132' ,#GroupNames output
print #GroupNames
the print statement looks like this :
'vQAHR','vQAResearch','vQAICT','vQAAdvancement','vAllResearch','vAllStudent','vQATeachLearn','vQAFinance','vQAHR'
(0 row(s) affected)
I have a predicate
WHERE
(
RLP.ALL_GROUP_NAME IN ( #GroupNames )
)
this doesn't seem to work how should I format the string with ' so that it
works. If I copy that string and paste it in place of #GroupNames in the predicate it works file just something in the substitution that seems to screw it up.

You need a split string function
In Sql Server 2016 you can use STRING_SPLIT function
WHERE
(
RLP.ALL_GROUP_NAME IN ( select value from STRING_SPLIT(#GroupNames,','))
)
For previous version use any one of the method from below link
Split strings the right way – or the next best way
In case you are appending single quotes for each value inside the string then use this
WHERE
(
RLP.ALL_GROUP_NAME IN ( select stuff(stuff(value,1,1,''),len(value)-1,1,'') from STRING_SPLIT(#GroupNames,','))
)
or you can use Dynamic Sql, Considering there wont be any Sql injection since it is a procedure output
declare #sql varchar(max)=''
set #sql ='
select ..
WHERE
RLP.ALL_GROUP_NAME IN ( '+#str+') '
exec (#sql)

If not 2016, you can use a TVF to split or parse the string
-- Notice the single quotes surrounding each item are not necessary
Declare #GroupNames varchar(max) = 'vQAHR,vQAResearch,vQAICT,vQAAdvancement,vAllResearch,vAllStudent,vQATeachLearn,vQAFinance,vQAHR'
Select A.*
From YourTable A
Where RLP.ALL_GROUP_NAME in (Select RetVal from [dbo].[udf-Str-Parse](#GroupNames,','))
The UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Now, Just for fun, if you don't want a UDF, you can
Select A.*
From YourTable A
Where RLP.ALL_GROUP_NAME In (Select Item=LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#GroupNames,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
)

Related

Split string into two columns with delimiter ->

I have the following given string to split into two columns with given From and To format.
Given string:
DECLARE #String VARCHAR(MAX) = 'A->B->C->D'
Expected Result:
From To
-----------
A B
B C
C D
Tried:
DECLARE #String VARCHAR(MAX) = 'A->B->C->D'
SELECT CASE WHEN item LIKE '%-' THEN REPLACE(item,'-','') END AS [From],
CASE WHEN item NOT LIKE '%-' THEN item END AS [To]
FROM dbo.f_Split(#String,'>')
Try this:
DECLARE #String VARCHAR(MAX) = 'A->B->C->D';
DECLARE #StringXML XML = CAST('<a>' + REPLACE(#String, '->', '</a><a>') + '</a>' AS XML);
WITH DataSource ([RowID], [RowValue]) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY T.c ASC)
,T.c.value('.', 'CHAR(1)')
FROM #StringXML.nodes('a') T(c)
)
SELECT DS1.[RowValue] AS [From]
,DS2.[RowValue] AS [TO]
FROM DataSource DS1
INNER JOIN DataSource DS2
ON DS1.[RowID] + 1 = DS2.[RowID];
The idea is to split the values and order them. Then just perform join to the final row set to itself.
You can REPLACE the string before processing it and directly apply joins to get the expected output. Considering the dbo.f_Split function returns column item.
DECLARE #String VARCHAR(MAX) = 'A->B->C->D->E->F->G';
SET #String = REPLACE(#String, '->', '>')
WITH CTE(RowNumber, RowData) AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY S1.item) AS RowNumber,
S1.item AS RowData
FROM dbo.f_Split(#String,'>') S1
)
SELECT
C1.RowData AS [From],
C2.RowData AS [To]
FROM CTE C1
INNER JOIN CTE C2 ON C1.RowNumber + 1 = C2.RowNumber
One more solution using the position and +1:
DECLARE #String VARCHAR(MAX) = 'A->B->C->D->E';
DECLARE #YourStringAsXml XML=CAST('<x>' + REPLACE(#String, '->', '</x><x>') + '</x>' AS XML);
--the query
WITH tally(nr) AS
(
SELECT TOP (#YourStringAsXml.value('count(/x)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values
)
SELECT #YourStringAsXml.value('/x[sql:column("nr")][1]','varchar(10)') AS FromNode
,#YourStringAsXml.value('/x[sql:column("nr")+1][1]','varchar(10)') AS ToNode
FROM tally;
The idea in short:
We transform the string to an XML
We use a tally-on-the-fly with a computed TOP() clause to get a list of running numbers (better was - and very handsome anyway - a pyhsical numbers table).
Now we can pick the elements by their position (sql:column()) and the neighbour by simply adding +1 to this position

Select rows with any member of list of substrings in string

In a Micrososft SQL Server table I have a column with a string.
Example:
'Servernamexyz.server.operationunit.otherstuff.icouldnt.predict.domain.domain2.domain3'
I also have a dynamic list of substrings
Example:
('icouldnt', 'stuff', 'banana')
I don't care for string manipulation. The substrings could also be called:
('%icouldnt%', '%stuff%', '%banana%')
What's the best way to find all rows where the string contains one of the substrings?
Solutions that are not possible:
multiple OR Statements in the WHERE clause, the list is dynamic
external Code to do a "for each", its a multi value parameter from the reportbuilder, so nothing useful here
changing the database, its the database of a tool a costumer is using and we can't change it, even if we would like... so much
I really cant believe how hard such a simple problem can turn out. It would need a "LIKE IN" command to do it in a way that looks ok. Right now I cant think of anything but a messy temp table.
One option is to use CHARINDEX
DECLARE #tab TABLE (Col1 NVARCHAR(200))
INSERT INTO #tab (Col1)
VALUES (N'Servernamexyz.server.operationunit.otherstuff.icouldnt.predict.domain.domain2.domain3' )
;WITH cteX
AS(
SELECT 'icouldnt' Strings
UNION ALL
SELECT 'stuff'
UNION ALL
SELECT 'banana'
)
SELECT
T.*, X.Strings
FROM #tab T
CROSS APPLY (SELECT X.Strings FROM cteX X) X
WHERE CHARINDEX(X.Strings, T.Col1) > 1
Output
EDIT - using an unknown dynamic string variable - #substrings
DECLARE #tab TABLE (Col1 NVARCHAR(200))
INSERT INTO #tab (Col1)
VALUES (N'Servernamexyz.server.operationunit.otherstuff.icouldnt.predict.domain.domain2.domain3' )
DECLARE #substrings NVARCHAR(200) = 'icouldnt,stuff,banana'
SELECT
T.*, X.Strings
FROM #tab T
CROSS APPLY
( --dynamically split the string
SELECT Strings = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#substrings, ',', '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
) X
WHERE CHARINDEX(X.Strings, T.Col1) > 1

SQL Server Stored Procedure to loop through comma seperated list

Is it possible to create a stored procedure that will split a comma separated list and then loop through the list and perform update statements?
This is just 3 updates of what runs into several hundred update statements that is in the region of 120,000 chars long, that are executed concurrently:
UPDATE OPERATION
SET START = '20151012', FINISH = '20151012'
WHERE REF = '912^0^15';
UPDATE OPERATION
SET START = '20151012', FINISH = '20151013'
WHERE REF = '913^0^15';
UPDATE OPERATION
SET START = '20151013', FINISH = '20151014'
WHERE REF = '872^0^15';
What I am thinking is instead, passing a list to a procedure and then have it create the update statements, thereby reducing the length of each update from 111 chars per record to around 30 chars.
I don't know how to write stored procedures in SQL Server, but in javascript I would do it something like this:
Pseudo code:
list = "'20151012','20151012','912^0^15'|'20151012', '20151013','913^0^15'|'20151013','20151014','872^0^15'"
for each list.split('|') as row
cols = row.split(',')
UPDATE OPERATION
SET START = 'cols[0]', FINISH = 'cols[1]'
WHERE REF = 'cols[2]';
Is it possible to create a stored procedure that can do this?
I don't like your idea at all. But to prove that it is possible I've prepared small demo:
LiveDemo
DECLARE #string NVARCHAR(MAX) =
'20151012,20151012,912^0^15|20151012, 20151013,913^0^15|20151013,20151014,872^0^15';
CREATE TABLE #OPERATION(START NVARCHAR(100), FINISH NVARCHAR(100), REF NVARCHAR(100));
INSERT INTO #OPERATION
VALUES ('', '', '912^0^15'),
('', '', '913^0^15'),
('', '', '872^0^15');
WITH cte AS
(
SELECT
xml_data = CAST(REPLACE(REPLACE('<d>'+REPLACE(REPLACE(#string,'|','</d><d>'), ',' ,'</e><e>') +'</d>', '</d>', '</e></d>'), '<d>', '<d><e>') AS XML)
) , cte2 AS (
SELECT record.r.value('e[1]', 'NVARCHAR(100)') AS [start],
record.r.value('e[2]', 'NVARCHAR(100)') AS [finish],
record.r.value('e[3]', 'NVARCHAR(100)') AS [ref]
FROM cte
CROSS APPLY xml_data.nodes('//d') AS record(r)
)
UPDATE o
SET Start = c.[start]
,finish = c.[finish]
FROM #OPERATION o
JOIN cte2 c
ON c.ref = o.ref;
SELECT *
FROM #Operation;
You can wrap it with stored procedure where #string is parameter.
StoredProcedureDemo
CREATE PROCEDURE dbo.my_custom_update
#string NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
WITH cte AS
(
SELECT
xml_data = CAST(REPLACE(REPLACE('<d>'+REPLACE(REPLACE(#string,'|','</d><d>'), ',' ,'</e><e>') +'</d>', '</d>', '</e></d>'), '<d>', '<d><e>') AS XML)
) , cte2 AS (
SELECT record.r.value('e[1]', 'NVARCHAR(100)') AS [start],
record.r.value('e[2]', 'NVARCHAR(100)') AS [finish],
record.r.value('e[3]', 'NVARCHAR(100)') AS [ref]
FROM cte
CROSS APPLY xml_data.nodes('//d') AS record(r)
)
UPDATE o
SET Start = c.[start]
,finish = c.[finish]
FROM OPERATION o
JOIN cte2 c
ON c.ref = o.ref;
END;
GO
Try using XML like this
DECLARE #list VARCHAR(MAX) = '''20151012'',''20151012'',''912^0^15''|''20151012'', ''20151013'',''913^0^15''|''20151013'',''20151014'',''872^0^15'''
SELECT s.rowno
, MAX(CASE WHEN s.colno = 1 THEN s.value END) AS start
, MAX(CASE WHEN s.colno = 2 THEN s.value END) AS finish
, MAX(CASE WHEN s.colno = 3 THEN s.value END) AS ref
FROM (
SELECT c.rowno
, ROW_NUMBER() OVER (PARTITION BY c.rowno ORDER BY (SELECT 1))
, REPLACE(LTRIM(d.x.value('(./text())[1]', 'VARCHAR(MAX)')), '''', '')
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1))
, CONVERT(XML, '<i>' + REPLACE(b.x.value('(./text())[1]', 'VARCHAR(MAX)'), ',', '</i><i>') + '</i>')
FROM (
SELECT CONVERT(XML, '<i>' + REPLACE(#list, '|', '</i><i>') + '</i>')
) a(x)
CROSS APPLY a.x.nodes('i') b(x)
) c(rowno, x)
CROSS APPLY c.x.nodes('i') d(x)
) s(rowno, colno, value)
GROUP BY s.rowno

How to remove first N specific characters if they all are zeros

I have this type of strings
01/CBA/1234567890
02/ABC/0000969755
06/DEF/0000000756
I want to remove the zeroes and get following output
01/CBA/1234567890
02/ABC/969755
06/DEF/756
How can i do that ? I though about combination of RIGHT, LEFT, CHARINDEX, SUBSTRING functions but have no idea how to combine them.
Any idea ?
You can use something like this:
SUBSTRING(myString, PATINDEX('%[^0]%', myString+'.'), LEN(myString))
Below Query will help you
DECLARE #var VARCHAR(500) = '06/DEF/0000000756'
SELECT
LEFT(#var, CHARINDEX('/',#var)) +
LEFT(REPLACE(#var, LEFT(#var, CHARINDEX('/',#var)),''),
CHARINDEX('/',REPLACE(#var, LEFT(#var, CHARINDEX('/',#var)) ,'')))
+
CONVERT(varchar, CONVERT(numeric(18,0),REPLACE(REPLACE(#var,LEFT(#var,
CHARINDEX('/',#var)),''),Left(REPLACE(#var,LEFT(#var,
CHARINDEX('/',#var)) ,''),
CHARINDEX('/',REPLACE(#var,LEFT(#var,
CHARINDEX('/',#var)),''))),'')))
Try this one
Method 1
DECLARE #STR VARCHAR(100)= '02/ABC/0000969755'
SELECT SUBSTRING(#STR,0,LEN(#STR) - CHARINDEX('/', REVERSE(#STR)) + 2)+
CAST(CAST(REVERSE(SUBSTRING(REVERSE(#STR),0,CHARINDEX('/',REVERSE(#STR))))AS INT)AS VARCHAR(20))
Click here to view result
Method 2
DECLARE #STR VARCHAR(100)= '02/ABC/0000969755'
;WITH CTE AS
(
-- Convert to rows
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RNO,
LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'STRING'
FROM
(
SELECT CAST ('<M>' + REPLACE(#STR, '/', '</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
,CTE2 AS
(
-- Convert to int and then varchar
SELECT RNO,CASE WHEN RNO = 3 THEN CAST(CAST(STRING AS INT)AS VARCHAR(40)) ELSE STRING END STRR
FROM CTE
)
-- Convert back to / separated values
SELECT SUBSTRING(
(SELECT '/ ' + STRR
FROM CTE2
ORDER BY RNO
FOR XML PATH('')),2,200000) STRING
Click here for working solution
use left and right string function
DECLARE #str varchar(20)='06/DEF/0000000756'
SELECT left(#str,7)+convert(varchar(20),convert(int,right(#str,10)))

Remove second appearence of a substring from string in SQL Server

I need to remove the second appearance of a substring from the main string, IF both substrings are next to each other. e.g.:
Jhon\Jhon\Jane\Mary\Bob needs to end Jhon\Jane\Mary\Bob
but Mary\Jane\Mary\Bob has to remain unchanged.
Can anyone can come out with a performant way to do this?
'\' is the separator of different names, so it can be use as limit of the substring to replace.
EDIT: this is to be run on a SELECT statement, so it should be a one line solution, I can't use variables.
Also, if the names are repetaed anywhere else, I have to let them there. Only remove one occurrence if both the first and the second names are the same.
So here is one try, but as I said, I don't think you will get a fast solution in native T-SQL.
First, if you don't already have a numbers table, create one:
SET NOCOUNT ON;
DECLARE #UpperLimit int = 4000;
;WITH n AS
(
SELECT rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
)
SELECT [Number] = rn - 1
INTO dbo.Numbers FROM n
WHERE rn <= #UpperLimit + 1;
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers([Number]);
Then create two functions. One that splits strings apart into a table, and then another that re-joins the results of the first function but ignores any subsequent duplicates.
CREATE FUNCTION dbo.SplitStrings
(
#List nvarchar(4000),
#Delim char(1)
)
RETURNS TABLE
AS
RETURN ( SELECT
rn = ROW_NUMBER() OVER (ORDER BY CHARINDEX(#Delim, #List + #Delim)),
[Value] = LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim, #List + #Delim, [Number]) - [Number])))
FROM dbo.Numbers
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim + #List, [Number], 1) = #Delim
);
GO
Second function:
CREATE FUNCTION dbo.RebuildString
(
#List nvarchar(4000),
#Delim char(1)
)
RETURNS nvarchar(4000)
AS
BEGIN
RETURN ( SELECT newval = STUFF((
SELECT #Delim + x.[Value] FROM dbo.SplitStrings(#List, #Delim) AS x
LEFT OUTER JOIN dbo.SplitStrings(#List, #Delim) AS x2
ON x.rn = x2.rn + 1
WHERE (x2.rn IS NULL OR x.value <> x2.value)
ORDER BY x.rn
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
);
END
GO
Now you can try it against the two samples you gave in your question:
;WITH cte(colname) AS
(
SELECT 'Jhon\Jhon\Jane\Mary\Bob'
UNION ALL SELECT 'Mary\Jane\Mary\Bob'
)
SELECT dbo.RebuildString(colname, '\')
FROM cte;
Results:
Jhon\Jane\Mary\Bob
Mary\Jane\Mary\Bob
But I strongly, strongly, strongly recommend you thoroughly test this against your typical data size before deciding to use it.
I decided to go for string manipulation. I thought it'd take longer to execute the query, but testing it in... ejem... production environment... ejem... I found out that it did not (much to my surprise). It ain't pretty, I know, but it's easy to mantain...
Here is a simplified version of my final query:
SELECT SOQ.PracticeId,
CASE WHEN LEFT(SOQ.myString, SOQ.SlashPos) = SUBSTRING(SOQ.myString, SOQ.SlashPos + 1, LEN(LEFT(SOQ.myString, SOQ.SlashPos)))
THEN RIGHT(SOQ.myString, LEN(SOQ.myString) - SOQ.SlashPos)
ELSE SOQ.myString
END as myString
FROM (SELECT OQ.AllFields, OQ.myString, CHARINDEX('\', OQ.myString, 0) as SlashPos
FROM MyOriginalQuery OQ) SOQ

Resources