SQL Server string search and evaluate - sql

In SQL Server I want to evaluate a string like 1.2.30.4.50 and output with 0102300450. Basically evaluate each number before the period and left pad it with a zero if it has a single digit and output two digits. If the number already has two digits it should be left untouched.
A string like 2.5.99.10.1 should evaluate to 0205991001. Note there are no zeros before 99 and 10.
I know the below query will return the 1st number before the first period occurrence. But I'm unable to take this further. Any help appreciated!
If col contains a string say 1.2.30.4.50:
SELECT left(col, charindex('.', col) - 1) FROM table
will return the below:
column1
----------
1

You can as the below
CREATE FUNCTION PrepareString
(
#val varchar(max),
#delimiter varchar(10)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #xml as xml
SET #xml = cast(('<X>'+replace(#val,#delimiter ,'</X><X>')+'</X>') as xml)
RETURN
(
SELECT
(
SELECT
CASE WHEN LEN(N.value('.', 'varchar(MAX)')) = 1 THEN '0' + N.value('.', 'varchar(MAX)') ELSE N.value('.', 'varchar(MAX)') END
FROM #xml.nodes('X') as T(N)
FOR XML PATH ('')
) A
)
END
GO
Example:
SELECT dbo.PrepareString('2.5.99.10.1', '.') -- 0205991001
SELECT dbo.PrepareString('1.2.30.4.50', '.') -- 0102300450
SELECT dbo.PrepareString('1.2.30.4.50.123.3.443.2', '.') -- 01023004501230344302

My first thought was to use ParseName(), but there are too many observations
Declare #String varchar(50) = '2.5.99.10.1'
Select right('0'+Pos1,2)+right('0'+Pos2,2)+right('0'+Pos3,2)+right('0'+Pos4,2)+right('0'+Pos5,2)
From [dbo].[udf-Str-Parse-Row](#String,'.')
Returns
0205991001
The Row Parse UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos1 = xDim.value('/x[1]','varchar(max)')
,Pos2 = xDim.value('/x[2]','varchar(max)')
,Pos3 = xDim.value('/x[3]','varchar(max)')
,Pos4 = xDim.value('/x[4]','varchar(max)')
,Pos5 = xDim.value('/x[5]','varchar(max)')
,Pos6 = xDim.value('/x[6]','varchar(max)')
,Pos7 = xDim.value('/x[7]','varchar(max)')
,Pos8 = xDim.value('/x[8]','varchar(max)')
,Pos9 = xDim.value('/x[9]','varchar(max)')
From (Select Cast('<x>' + Replace(#String,#Delimiter,'</x><x>')+'</x>' as XML) as xDim) A
)
--Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')

Assuming you are not on SQL Server 2016 and can't use the string_split function, this type of problem is perfectly suited to Jeff Moden's Tally Table based string split table valued function.
To create the function:
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
GO
And then combine with for xml to concatenate the rows to columns to get your result.
-- Create some data.
declare #a table(String nvarchar(100));
insert into #a values
('1.2.30.4.50')
,('06.7.80.90.10')
,('01.20.3.40.5');
-- This is how the function returns the data.
select Item
,right('0' + cast(Item as varchar(10)),2) as PadItem
from dbo.DelimitedSplit8K('1.2.30.4.50','.');
-- This is how you use FOR XML to concatenate the rows back together.
select (select '' + right('0' + cast(Item as varchar(10)),2)
from dbo.DelimitedSplit8K(a.String,'.')
for xml path('')
) as Result
from #a a;
Which give your the fllowing results:
Result
----------
0102300450
0607809010
0120034005

This solution is based on the following:
Number of tokens: 5
Min token length: 1
Max token length: 2
select right ('0' + ltrim (rtrim (substring (replace (col,'.',' '), 1,2))),2)
+ right ('0' + ltrim (rtrim (substring (replace (col,'.',' '), 6,3))),2)
+ right ('0' + ltrim (rtrim (substring (replace (col,'.',' '),11,4))),2)
+ right ('0' + ltrim (rtrim (substring (replace (col,'.',' '),16,5))),2)
+ right ('0' + ltrim (rtrim (substring (replace (col,'.',' '),21,6))),2)
from tab
;

Try this on below:
DECLARE #Str VARCHAR(100) = '2.5.99.10.1' DECLARE #EvlStr VARCHAR(100) = '',#OutPut VARCHAR(100) = ''
WHILE LEN(#Str) > 0
BEGIN
IF CHARINDEX('.',#Str) = 0
BEGIN
SET #EvlStr = #Str
SET #Str = ''
END
ELSE
BEGIN
SELECT #EvlStr = SUBSTRING(#Str,0,CHARINDEX('.',#Str))
SELECT #Str = SUBSTRING(#Str,CHARINDEX('.',#Str)+1,LEN(#Str))
END
IF LEN(#EvlStr) = 1
SET #OutPut = #OutPut + '0' + #EvlStr
ELSE
SET #OutPut = #OutPut + #EvlStr
END
SELECT #OutPut

Related

How to replace anything between 2 specific characters in SQL Server

I'm trying to replace anything between 2 specific characters in a string that contains multiples of those 2 caracters. Take it as a csv format.
Here an example of what i got as data in that field:
0001, ABCD1234;0002, EFGH432562;0003, IJKL1345hsth;...
What I need to retreive from it is all parts before the ',' but not what are between ',' and ';'
I tried with those formula but no success
SELECT REPLACE(fieldname, ',[A-Z];', ' ') FROM ...
or
SELECT REPLACE(fieldname, ',*;', ' ') FROM ...
I need to get
0001 0002 0003
Is there a way to achieve that?
You can CROSS APPLY to a STRING_SPLIT that uses STRING_AGG (since Sql Server 2017) to stick the numbers back together.
select id, codes
from your_table
cross apply (
select string_agg(left(value, patindex('%_,%', value)), ' ') as codes
from string_split(fieldname, ';') s
where value like '%_,%'
) ca;
GO
id
codes
1
0001 0002 0003
Demo on db<>fiddle here
Extra
Here is a version that also works in Sql Server 2014.
Inspired by the research from #AaronBertrand
The UDF uses a recursive CTE to split the string.
And the FOR XML trick is used to stick the numbers back together.
CREATE FUNCTION dbo.fnString_Split
(
#str nvarchar(4000),
#delim nchar(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH RCTE AS (
SELECT
1 AS ordinal
, ISNULL(NULLIF(CHARINDEX(#delim, #str),0), LEN(#str)) AS pos
, LEFT(#str, ISNULL(NULLIF(CHARINDEX(#delim, #str),0)-1, LEN(#str))) AS value
UNION ALL
SELECT
ordinal+1
, ISNULL(NULLIF(CHARINDEX(#delim, #str, pos+1), 0), LEN(#str))
, SUBSTRING(#str, pos+1, ISNULL(NULLIF(CHARINDEX(#delim, #str, pos+1),0)-pos-1, LEN(#str)-pos ))
FROM RCTE
WHERE pos < LEN(#str)
)
SELECT ordinal, value
FROM RCTE
);
SELECT id, codes
FROM your_table
CROSS APPLY (
SELECT RTRIM((
SELECT LEFT(value, PATINDEX('%_,%', value))+' '
FROM dbo.fnString_Split(fieldname, ';') AS spl
WHERE value LIKE '%_,%'
ORDER BY ordinal
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
) AS codes
) ca
OPTION (MAXRECURSION 250);
id
codes
1
0001 0002 0003
Demo on db<>fiddle here
Alternative version of the UDF (no recursion)
CREATE FUNCTION dbo.fnString_Split
(
#str NVARCHAR(4000),
#delim NCHAR(1)
)
RETURNS #tbl TABLE (ordinal INT, value NVARCHAR(4000))
WITH SCHEMABINDING
AS
BEGIN
DECLARE #value NVARCHAR(4000)
, #pos INT = 0
, #ordinal INT = 0;
WHILE (LEN(#str) > 0)
BEGIN
SET #ordinal += 1;
SET #pos = ISNULL(NULLIF(CHARINDEX(#delim, #str),0), LEN(#str)+1);
SET #value = LEFT(#str, #pos-1);
SET #str = SUBSTRING(#str, #pos+1, LEN(#str));
INSERT INTO #tbl (ordinal, value)
VALUES (#ordinal, #value);
END;
RETURN;
END;
If you're on SQL Server 2017 and don't need a guarantee that the order will be maintained, then LukStorms' answer is perfectly adequate.
However, if you:
care about an order guarantee; or,
are on an older version than 2017 (and can't use STRING_AGG); or,
are on an even older version than 2016 or are in an older compatibility level (and can't use STRING_SPLIT):
Here's an ordered split function that can help (it's long and ugly but you only have to create it once):
CREATE FUNCTION dbo.SplitOrdered
(
#list nvarchar(max),
#delim nvarchar(10)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH w(n) AS (SELECT 0 FROM (VALUES (0),(0),(0),(0)) w(n)),
k(n) AS (SELECT 0 FROM w a, w b),
r(n) AS (SELECT 0 FROM k a, k b, k c, k d, k e, k f, k g, k h),
p(n) AS (SELECT TOP (COALESCE(LEN(#list), 0))
ROW_NUMBER() OVER (ORDER BY ##SPID) -1 FROM r),
spots(p) AS
(
SELECT n FROM p
WHERE (SUBSTRING(#list, n, LEN(#delim + 'x') - 1) LIKE #delim OR n = 0)
),
parts(p,val) AS
(
SELECT p, SUBSTRING(#list, p + LEN(#delim + 'x') - 1,
LEAD(p, 1, 2147483647) OVER (ORDER BY p) - p - LEN(#delim))
FROM spots AS s
)
SELECT listpos = ROW_NUMBER() OVER (ORDER BY p),
Item = LTRIM(RTRIM(val))
FROM parts
);
Then the query can become:
;WITH x AS
(
SELECT id, listpos,
codes = LEFT(Item, COALESCE(NULLIF(CHARINDEX(',', Item),0),1)-1)
FROM dbo.your_table
CROSS APPLY dbo.SplitOrdered(fieldname, ';') AS c
)
SELECT id, codes = (
(SELECT x2.codes + ' '
FROM x AS x2
WHERE x2.id = x.id
ORDER BY x2.listpos
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)')
)
FROM x GROUP BY id;
Example borrowing from LukStorms' db<>fiddle
Note that, in addition to guaranteeing order and being backward compatible (well, only back so many versions), it also ignores garbage data, e.g. try:
0001, ABCD1234;0002 but no comma

SQL Server: Split string value with single quotations

Trying to modify a set of code, so that given a string, the string has to be split and passed to the code to be used.
This is the code that I have right now.
DECLARE #xml xml,
#str varchar(100),
#delimiter varchar(10)
SET #str = '100'
SET #delimiter = ','
SET #xml = cast(('<X>'+replace(#str, #delimiter, '</X><X>')+'</X>') as
xml)
SELECT C.value('.', 'varchar(10)') as value
FROM #xml.nodes('X') as X(C)
For a single-valued string, this works just fine. But I need to use more than one like, ('100', '100A', '100B'...).
The string value will not contain anything other than 3-digit numbers or 3-digit numbers + an alphabet character, or 3 alphabet letter characters.
I also tried something else, but this is too slow.
declare #values table
(
Value varchar(1000)
)
insert into #values values ('100'),('100A'),('100B'),('100C')
Select *
from table
where myField in (select value from #value)
How can I modify the code for this requirement?
You need to create a table valued function that you can pass your string to split into using a cross apply:
Function
create function [dbo].[fn_StringSplit4k]
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(20) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return.
)
returns table
as
return
-- Start tally table with 10 rows.
with n(n) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
-- Select the same number of rows as characters in #str as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest #str length.
,t(t) as (select top (select len(isnull(#str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
--,s(s) as (select 1 union all select t+len(replace(#delimiter,' ','.')) from t where substring(isnull(#str,''),t,len(replace(#delimiter,' ','.'))) = #delimiter)
,s(s) as (select 1 union all select t+1 from t where case when #delimiter = '' and t < len(#str) then 1 else case when substring(isnull(#str,''),t,1) = #delimiter then 1 else 0 end end = 1)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,case when #delimiter = '' then 1 else isnull(nullif(charindex(#delimiter,isnull(#str,''),s),0)-s,4000) end from s)
select rn
,item
from(select row_number() over(order by s) as rn
,substring(#str,s,l) as item
from l
) a
where rn = #num
or #num is null;
Usage
select s.item
from YourTable as t
cross apply dbo.fn_StringSplit4k(t.YourString,',',null) as s;

SQL Server - Split column data and retrieve last second value

I have a column name MasterCode in XYZ Table where data is stored in below form.
.105248.105250.104150.111004.
Now first of all I want to split the data into :
105248
105250
104150
111004
Then after to retrieve only last second value from the above.
So In the above given array, value returned should be 104150.
Use a split string function, but not the built in once since it will return only the values and you will lose the location data.
You can use Jeff Moden's DelimitedSplit8K that will return the item and the item index:
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
Then you can use it to split the string and it will return a table like this:
DECLARE #string varchar(100) = '.105248.105250.104150.111004.';
SELECT *
FROM [dbo].[DelimitedSplit8K](#string, '.')
ItemNumber Item
1
2 105248
3 105250
4 104150
5 111004
6
You want only the parts where there actually is an item, so add a where clause, and you want the second from last so add row_number(), and you want the entire thing in a common table expression so that you can query it:
DECLARE #string varchar(100) = '.105248.105250.104150.111004.';
WITH CTE AS
(
SELECT Item, ROW_NUMBER() OVER(ORDER BY ItemNumber DESC) As rn
FROM [dbo].[DelimitedSplit8K](#string, '.')
WHERE Item <> ''
)
And the query:
SELECT Item
FROM CTE
WHERE rn = 2
Result: 104150
If there are always four parts, you can use PARSENAME():
DECLARE #s varchar(64) = '.105248.105250.104150.111004.';
SELECT PARSENAME(SUBSTRING(#s, 2, LEN(#s)-2),2);
Depending on your version of SQL SERVER, you can also use the STRING_SPLIT function.
DECLARE #string varchar(100) = '.105248.105250.104150.111004.';
SELECT value,
ROW_NUMBER() OVER (ORDER BY CHARINDEX('.' + value + '.', '.' + #string + '.')) AS Pos
FROM STRING_SPLIT(#string,'.')
WHERE RTRIM(value) <> '';
It doesn't return the original position like Jeff's splitter, but does compare very favourably if you check Aaron Bertrand's Article :
Performance Surprises and Assumptions : STRING_SPLIT()
Edit:
Added position, but although works in this case may have issues with duplicate values
You can create a SQL server table valued function with parameters stringvalue and delemeter and call that function for the results as expected.
ALTER function [dbo].[SplitString]
(
#str nvarchar(4000),
#separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
1,
1,
charindex(#separator, #str)
union all
select
p + 1,
b + 1,
charindex(#separator, #str, b + 1)
from tokens
where b > 0
)
select
p-1 ID,
substring(
#str,
a,
case when b > 0 then b-a ELSE 4000 end)
AS s
from tokens
)
To call the function
SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> ''
Output
ID s
1 105248
2 105250
3 104150
4 111004
To get only second value you can write your query as shown below
DECLARE #MaxID INT
SELECT #MaxID = MAX (ID) FROM (SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> '') A
SELECT TOP 1 #MaxID = MAX (ID) FROM (
SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> ''
)a where ID < #MaxID
SELECT * FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> '' AND ID = #MaxID
Output
ID s
3 104150
If you want 1 as value of ID then you can write your query as shown below in last line of query.
SELECT 1 AS ID , S FROM [DBO].[SPLITSTRING] ('.105248.105250.104150.111004.', '.') WHERE ISNULL(S,'') <> '' AND ID = #MaxID
Then the output will be
ID S
1 104150
Hope this will help you.
Try this
DECLARE #DATA AS TABLE (Data nvarchar(1000))
INSERT INTO #DATA
SELECT '.105248.105250.104150.111004.'
;WITH CTE
AS
(
SELECT Data,ROW_NUMBER()OVER(ORDER BY Data DESC) AS Rnk
FROM
(
SELECT Split.a.value('.','nvarchar(100)') Data
FROM(
SELECT CAST('<S>'+REPLACE(Data,'.','</S><S>')+'</S>' AS XML ) As Data
FROM #DATA
)DT
CROSS APPLY Data.nodes('S') AS Split(a)
) AS Fnl
WHERE Fnl.Data <>''
)
SELECT Data FROM CTE
WHERE Rnk=2
Result
Data
-----
105248
105250
104150
111004
It can also be achieve only using string functions:
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
SELECT '.105248.105250.104150.111004.' code INTO #temp UNION ALL
SELECT '.205248.205250.204150.211004.'
SELECT
REVERSE(LEFT(
REVERSE(LEFT(code, LEN(code) - CHARINDEX('.', REVERSE(code), 2)))
, CHARINDEX('.',REVERSE(LEFT(code, LEN(code) - CHARINDEX('.', REVERSE(code), 2)))) -1
)
) second_last_value
FROM #temp
Result:
second_last_value
-----------------------------
104150
204150

Extract All Instances of String into Concatenated Result

Using SQL Server 2014, I'm wanting to search within a field and return all instances of a string that is found, plus the following word. For example, the text in the column may be:
"exec sproc1 and then some more text here and then maybe execute sproc2 exec storedproc3 and maybe exec sproc1"
I'd like to elegantly return "sproc1, sproc2, storedproc3, sproc1", as each was the word following either exec or execute (as delimited by spaces). As you can see in the example, the leading word may vary, as may the length of the sproc name. I've been able to return the first usage of exec/execute; my issue is that sometimes there are multiple (see below).
REPLACE(REPLACE(CASE
WHEN [sJSTP].[subsystem]='TSQL' AND CHARINDEX('EXECUTE',[sJSTP].[command],1)>0
THEN SUBSTRING([sJSTP].[command],CHARINDEX('EXECUTE',[sJSTP].[command],1)+8,
IIF(
CHARINDEX(' ',[sJSTP].[command],CHARINDEX('EXECUTE',[sJSTP].[command],1)+8)>0,
CHARINDEX(' ',[sJSTP].[command],CHARINDEX('EXECUTE',[sJSTP].[command],1)+8)-CHARINDEX('EXECUTE',[sJSTP].[command],1)-8,
LEN([sJSTP].[command])))
WHEN [sJSTP].[subsystem]='TSQL' AND CHARINDEX('EXEC',[sJSTP].[command],1)>0 AND CHARINDEX('DCEXEC',[sJSTP].[command],1)<=0
THEN SUBSTRING([sJSTP].[command],CHARINDEX('EXEC',[sJSTP].[command],1)+5,
IIF(
CHARINDEX(' ',[sJSTP].[command],CHARINDEX('EXEC',[sJSTP].[command],1)+5)>0,
CHARINDEX(' ',[sJSTP].[command],CHARINDEX('EXEC',[sJSTP].[command],1)+5)-CHARINDEX('EXEC',[sJSTP].[command],1)-5,
LEN([sJSTP].[command])))
END,'[',''),']','') AS sprocname
The ultimate use of this is parsing job commands from the msdb..sysjobsteps table to see what stored procedures are being used.
Edit: Add sample data
Sample 1:
exec quarterly_run 1, 'BW'
exec quarterly_run_2 1, 'QR '
exec quarterly_run 2, 'VAS'
exec quarterly_run 1, 'WR'
exec quarterly_run 3, 'RW'
exec quarterly_run_2 1, 'ASF'
exec quarterly_run_3 1, 'ALL'
Sample 2:
declare #rundate datetime, #rptqtr datetime, #qtr int
set #rundate = getdate()
set #rptqtr = '06/30/2016'
set #qtr = (select datediff(quarter,#rptqtr,#rundate))
exec quarterly_extract #qtr
Sample 3:
exec Daily_Sync_Process
exec Daily_Process
Just another inline option, and not limited to 8K
Example
Declare #YourTable table (ID int,SomeCol varchar(max))
Insert into #YourTable values
(1,'exec quarterly_run 1, ''BW'' exec quarterly_run_2 1, ''QR '' exec quarterly_run 2, ''VAS'' exec quarterly_run 1, ''WR'' exec quarterly_run 3, ''RW'' exec quarterly_run_2 1, ''ASF'' exec quarterly_run_3 1, ''ALL''')
,(2,'declare #rundate datetime, #rptqtr datetime, #qtr int
set #rundate = getdate() set #rptqtr = ''06/30/2016''
set #qtr = (select datediff(quarter,#rptqtr,#rundate))
exec quarterly_extract #qtr
')
,(3,'exec Daily_Sync_Process exec Daily_Process')
;with cte as (
Select A.ID
,C.*
From #YourTable A
Cross Apply (values (replace(replace(SomeCol,char(13),' '),char(10),' '))) B(CleanString)
Cross Apply (
Select RetSeq,RetVal = case when Lag(RetVal,1) over (Order by RetSeq) in ('Exec','Execute') then RetVal else null end
From (
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((Select replace(CleanString,' ','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) C1
) C
)
Select A.ID
,NewString = Stuff((Select ', ' +RetVal From cte Where ID=A.ID Order By RetSeq For XML Path ('')),1,2,'')
From cte A
Group By A.ID
Returns
ID NewString
1 quarterly_run, quarterly_run_2, quarterly_run, quarterly_run, quarterly_run, quarterly_run_2, quarterly_run_3
2 quarterly_extract
3 Daily_Sync_Process, Daily_Process
So if you want to get what is immediately following exec then I'd split on the space, and then use a self join. Here is code using the function below, which is Jeff Moden's splitter.
with cte as(
select
job_id
,step_name
,step_id
,s.ItemNumber
,s.Item
from msdb..sysjobsteps
--split on the space
cross apply dbo.DelimitedSplit8K(command,' ') s)
select
c.job_id
,c.step_id
,c.step_name
,c.Item
,c2.Item
from cte c
--self join to get exec myproc in the same row
full join
cte c2 on
c2.ItemNumber = c.ItemNumber + 1
and c.job_id = c2.job_id
and c.step_id = c2.step_id
--we only care where the base table has exec or execute (not executed, etc)
where c.Item = 'exec' or c.Item = 'execute'
order by
c.job_id, c.step_id, c.ItemNumber
It's important to realize this would fail when, for example, the command was exec someproc which has two spaces. You can fix that with a replace() but you'd have to nest this replace multiple times to account for as many spaces as you want. You'd handle that on the command column in the splitter function
--here we replace two spaces with 1 for the entire command
cross apply dbo.DelimitedSplit8K(replace(command,' ',' '),' ') s)
SPLITER FUNCTION
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000... enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
GO

MS SQL Server Get value between commas

I have a column in Table1 with string in it separated by commma:
Id Val
1 ,4
2 ,3,1,0
3 NULL
4 ,5,2
Is there a simple way to split and get any value from that column,
for example
SELECT Value(1) FROM Table1 should get
Id Val
1 4
2 3
3 NULL
4 5
SELECT Value(2) FROM Table1 should get
Id Val
1 NULL
2 1
3 NULL
4 2
Thank you!
Storing comma separated values in a column is always a pain, consider changing your table structure
To get this done, create a split string function. Here is one of the best possible approach to split the string to individual rows. Referred from http://www.sqlservercentral.com/articles/Tally+Table/72993/
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
to call the function
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 1
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 2
Another option using a Parse/Split Function and an OUTER APPLY
Example
Declare #YourTable Table ([Id] int,[Val] varchar(50))
Insert Into #YourTable Values
(1,',4')
,(2,',3,1,0')
,(3,NULL)
,(4,',5,2')
Select A.ID
,Val = B.RetVal
From #YourTable A
Outer Apply (
Select * From [dbo].[tvf-Str-Parse](A.Val,',')
Where RetSeq = 2
) B
Returns
ID Val
1 4
2 3
3 NULL
4 5
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-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((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Here is an example of using a CTE combined with converting the CSV to XML:
DECLARE #Test TABLE (
CsvData VARCHAR(10)
);
INSERT INTO #Test (CsvData)
VALUES
('1,2,3'),
(',4,5,7'),
(NULL),
(',3,');
WITH XmlData AS (
SELECT CONVERT(XML, '<val>' + REPLACE(CsvData, ',', '</val><val>') + '</val>') [CsvXml]
FROM #Test
)
SELECT xd.CsvXml.value('val[2]', 'VARCHAR(10)')
FROM XmlData xd;
This would output:
2
4
NULL
3
The column to display is controlled by the XPath query. In this case, val[2].
The main advantage here is that no user-defined functions are required.
Try This Logic Using recursive CTE
DECLARE #Pos INT = 2
DECLARE #T TABLE
(
Id INT,
Val VARCHAR(50)
)
INSERT INTO #T
VALUES(1,',4'),(2,',3,1,0'),(3,NULL),(4,',5,2')
;WITH CTE
AS
(
SELECT
Id,
SeqNo = 0,
MyStr = SUBSTRING(Val,CHARINDEX(',',Val)+1,LEN(Val)),
Num = REPLACE(SUBSTRING(Val,1,CHARINDEX(',',Val)),',','')
FROM #T
UNION ALL
SELECT
Id,
SeqNo = SeqNo+1,
MyStr = CASE WHEN CHARINDEX(',',MyStr)>0
THEN SUBSTRING(MyStr,CHARINDEX(',',MyStr)+1,LEN(MyStr))
ELSE NULL END,
Num = CASE WHEN CHARINDEX(',',MyStr)>0
THEN REPLACE(SUBSTRING(MyStr,1,CHARINDEX(',',MyStr)),',','')
ELSE MyStr END
FROM CTE
WHERE ISNULL(REPLACE(MyStr,',',''),'')<>''
)
SELECT
T.Id,
CTE.Num
FROM #T t
LEFT JOIN CTE
ON T.Id = cte.Id
AND SeqNo = #Pos
My Output for the above
Test Data
Declare #t TABLE (Id INT , Val VARCHAR(100))
INSERT INTO #t VALUES
(1 , '4'),
(2 , '3,1,0'),
(3 , NULL),
(4 , '5,2')
Function Definition
CREATE FUNCTION [dbo].[fn_xml_Splitter]
(
#delimited nvarchar(max)
, #delimiter nvarchar(1)
, #Position INT = NULL
)
RETURNS TABLE
AS
RETURN
(
SELECT Item
FROM (
SELECT Split.a.value('.', 'VARCHAR(100)') Item
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ItemNumber
FROM
(SELECT Cast ('<X>' + Replace(#delimited, #delimiter, '</X><X>')
+ '</X>' AS XML) AS Data
) AS t CROSS APPLY Data.nodes ('/X') AS Split(a)
)x
WHERE x.ItemNumber = #Position OR #Position IS NULL
);
GO
Function Call
Now you can call this function in two different ways.
1 . to get return an Item on a specific position, specify the position in the 3rd parameter of the function:
SELECT *
FROM #t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', 1)
2 . to get return all items, specify the key word DEFUALT in the 3rd parameter of the function:
SELECT *
FROM #t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', DEFAULT)