Get string between two specific characters - sql-server

I have a table fileslist with column Filetext which contains text data
create table fileslist
(
FileID int identity (1,1),
Filetext nvarchar(max)
)
insert into fileslist select 'this file(''SQL_SCALAR1'') has been created to test your tricks of (''SQL_SCALAR2'')'
insert into fileslist select 'this file(''SCALAR3'') created to test your tricks of (''SQL_SCALAR4'')'
insert into fileslist select 'this file(''SQL_SCALAR5'') has been created to test your tricks of (''SQL_SCALAR6'')'
insert into fileslist select 'this file(''SQL_7'') has been created'
insert into fileslist select 'this file(''SQL_SCALAR8'') has been created to test your tricks of (''SQL_SCALAR9''), ohh i have more text than other (''SQL_SCALAR10'')files'
I need help to read all records one by one to extract string between two specific characters '(' and ')' and Output should be
SQL_SCALAR1
SQL_SCALAR2
SCALAR3
SQL_SCALAR4
SQL_SCALAR5
SQL_SCALAR6
SQL_7
SQL_SCALAR8
SQL_SCALAR9
SQL_SCALAR10

The reason I would choose this approach is that it handles a dynamic number of the patterns you are searching for, per row, very well.
First I would use Jeff Moden's string splitter;
/*
* Jeff Moden's famous string spliiter
* http://www.sqlservercentral.com/articles/Tally+Table/72993/
*/
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, I would write the following;
SELECT SUBSTRING (
Item,
2,
CHARINDEX(')', Item) - 3
)
FROM FilesList
CROSS
APPLY [dbo].[DelimitedSplit8K](FileText, '(')
WHERE Item LIKE '''SQL%'
As requested in comment; (I'm not thrilled about butchering Jeff's code this way...)
CREATE FUNCTION [dbo].[DelimitedSplit8K_Butchered]
--===== Define I/O parameters
(#pString VARCHAR(8000), #DelimiterA CHAR(1), #DelimiterB 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) = #DelimiterA
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#DelimiterA,#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.
OriginalOutput AS
(
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
)
SELECT
Item = SUBSTRING (
Item,
1,
CHARINDEX(#DelimiterB, Item) - 1
)
FROM OriginalOutput
WHERE Item LIKE '%' + #DelimiterB + '%'
--AND SUBSTRING(#pString, l.N1, l.L1) LIKE '%' + #DelimiterB + '%'
;
GO
Used as follows;
SELECT *
FROM FilesList
CROSS
APPLY [dbo].[DelimitedSplit8K_Butchered](FileText, '(', ')')

declare #ss varchar(100)= 'this file(''SQL_SCALAR1'') has been created to test your tricks of (''SQL_SCALAR2'')'
Not sure how you want to display
SELECT SUBSTRING ( (LEFT(#ss,CHARINDEX(')',#ss) - 1)),CHARINDEX('(',#ss)+1,LEN(LEFT(#ss,CHARINDEX(')',#ss) - 1))),
SUBSTRING ((RIGHT(#ss,CHARINDEX('(',REVERSE(#ss)) - 1)),1,LEN(RIGHT(#ss,CHARINDEX('(',REVERSE(#ss)) - 1))-1)
OR
SELECT SUBSTRING ( (LEFT(#ss,CHARINDEX(')',#ss) - 1)),CHARINDEX('(',#ss)+1,LEN(LEFT(#ss,CHARINDEX(')',#ss) - 1)))
Union
SELECT SUBSTRING ((RIGHT(#ss,CHARINDEX('(',REVERSE(#ss)) - 1)),1,LEN(RIGHT(#ss,CHARINDEX('(',REVERSE(#ss)) - 1))-1)

this is the solution what you are looking for, cheer....
;WITH CTE AS
(
SELECT SUBSTRING(FILETEXT,CHARINDEX('(',FileText,1)+2,11) 'Result'
FROM FILESLIST
UNION ALL
SELECT * FROM
(
SELECT
SUBSTRING(
(SUBSTRING(FILETEXT,CHARINDEX(')',FILETEXT,1)+1,LEN(FILETEXT)-
CHARINDEX(')',FILETEXT,1)) )
,CHARINDEX('(',(SUBSTRING(FILETEXT,CHARINDEX(')',FILETEXT,1)+1,LEN(FILETEXT)-
CHARINDEX(')',FILETEXT,1)) ) ,1)+2,11
) 'Result'
FROM FILESLIST
) T1
WHERE Result like '%SQL%'
)
SELECT * FROM CTE
ORDER BY RESULT ASC

A solution with recursive CTE is
WITH REP AS (
SELECT FileID
, Filetext = REPLACE(Filetext, ')', '(')
FROM fileslist
), Splitter AS (
SELECT FileID
, FileText
, NextStart = CHARINDEX('(', FileText)
, Pos = 0
, Token = CAST(SubString(FileText, 0, CHARINDEX('(', FileText))
AS NVarchar(50))
FROM REP
UNION ALL
SELECT FileId
, FileText
, NextStart = CHARINDEX('(', FileText, NextStart + 1)
, Pos = Pos + 1
, Token = CAST(SubString(FileText, NextStart + 1
, CHARINDEX('(', FileText, NextStart + 1)
- NextStart - 1) AS NVarchar(50))
FROM Splitter
WHERE CHARINDEX('(', FileText, NextStart + 1) - NextStart - 1 > 0
)
SELECT FileID, Token
FROM REC
WHERE Pos % 2 = 1
order by FileID, Pos
The Splitter CTE split the rows using a single delimiter, so before use it we need to make the two delimiter the same. Only the odd token are returned by the main query because the even token are other part of the string, for example the string 'this file(''SQL_7'') has been created' will be returned by the Splitter like (with only the interesting columns):
Pos | Token
----+------------------
0 | this file
1 | SQL_7
2 | has been created

Related

SQL Server: How to query within a string?

I have a requirement where my client wants me to retrieve specific information from a text column
Following is the sample of the same
the student scored following result: class: 6 subject: result: english 80 math 23
science 45
The expected outcome needs to be like -
English Maths Science
80 23 45
I tried using string_split
select value from STRING_SPLIT( (select value from mytable where [student roll number] = 'SCH-01097') , ' ' )
but that only split the value into multiple rows that can't be queried.
I also tried using LTRIM with CHARINDEX approach, but the column have different text and not always organized. the initial text is different most of the time.
can this be done?
edit - I am close but just not there yet
So far I have reached here
SELECT VALUE FROM STRING_SPLIT ((select
substring(value, charindex('Block',value),1000)
from mytable where [rollnumber ] = 'SCH-01097'),' ') WHERE VALUE <> ' '
this gives me everything I need but in a single column
class6:
Subject
result
english
80
math
23
science
45
now how to make it in desired table form?
To maintain the order of the split values this answer uses DelimitedSplit8K. Something like this works.
[Edit] Instead of having specific strings in a CTE, the query now uses 'stems' to map multiple strings to the same class. For example, if English is entered as En it will still be mapped to English.
Table and data
drop table if exists #tTest;
go
create table #tTest(
string Varchar(256));
insert #tTest(string) values
('the student scored following result: class: 6 subject: result: english 80 math 23');
DelimitedSplit8k
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
;
Query
;with
stems_cte(stem, word) as (
select 'English', 'English' union all
select 'En', 'English' union all
select 'Math', 'Math' union all
select 'Maths', 'Math' union all
select 'Science', 'Science'),
splt_cte(string, str_val, ndx, lead_ndx, lead_len, rn) as (
select t.string, ds.Item, charndx.ndx,
lead(charndx.ndx) over (order by ds.ItemNumber),
lead(len(ds.[Item])) over (order by ds.ItemNumber),
ItemNumber
from #tTest t
cross apply dbo.DelimitedSplit8K(t.string, ' ') ds
cross apply (select charindex(ds.Item, t.string, 1) ndx) charndx
where Item <> ' '),
spec_rows_cte(word, ndx, lead_ndx, lead_len, rn) as (
select sp.word, sc.ndx, sc.lead_ndx, sc.lead_len, sc.rn
from splt_cte sc
join stems_cte sp on sc.str_val=sp.stem)
select max(case when src.word='English' then substring(sc.string, src.lead_ndx, src.lead_len) else null end) English,
max(case when src.word='Math' then substring(sc.string, src.lead_ndx, src.lead_len) else null end) Math,
max(case when src.word='Science' then substring(sc.string, src.lead_ndx, src.lead_len) else null end) Science
from splt_cte sc
join spec_rows_cte src on sc.rn=src.rn;
Output
English Math Science
80 23 NULL
You can insert the results from your query into a table variable with an Identity column, then get the next row for each required subset:
declare #tmp table (Id int identity, Value varchar(20))
Insert into #tmp (VALUE)
SELECT VALUE FROM STRING_SPLIT ((select
substring(value, charindex('Block',value),1000)
from mytable where [rollnumber ] = 'SCH-01097'),' ') WHERE VALUE <> ' '
select
English = (Select top 1 Value From #tmp where Id = (Select Id + 1 From #tmp where Value = 'english')),
Math = (Select top 1 Value From #tmp where Id = (Select Id + 1 From #tmp where Value = 'math')),
Science = (Select top 1 Value From #tmp where Id = (Select Id + 1 From #tmp where Value = 'science'))
Output:

return longest sequence of digits from a string in SQL Server 2012

I need a function to return the longest sequence of digits in a string, for example:
P0123/99282 returns 99282,
P9-123BB-12339 returns 12339,
12345/54321 returns 12345 (should return first instance when length is the same).
I have developed this but this is very slow, I wonder if there is something faster than this:
DECLARE #str NVARCHAR(40) = N'P0120993/123-AB1239'
DECLARE #x XML
;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),
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN (SELECT 1 as t UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS b),
n(Number) AS(SELECT n = ROW_NUMBER() OVER (ORDER BY n) FROM e2)
SELECT #x = CAST(N'<A>'+ REPLACE((SELECT CAST(CAST((
SELECT
CASE WHEN SUBSTRING(#str, Number, 1) like N'[^0-9]' THEN N' ' ELSE SUBSTRING(#str, Number, 1) end
FROM n
WHERE Number <= LEN(#str) FOR XML Path(''))
AS xml) AS nvarchar(max))),N' ',N'</A><A>')+ N'</A>' AS XML)
SELECT TOP 1
case when t.value('.', 'nvarchar(max)') = N'' then null else t.value('.', 'nvarchar(max)') end AS inVal
FROM
#x.nodes('/A') AS x(t)
ORDER BY
LEN(t.value('.', 'nvarchar(max)')) DESC;
EXPLANATION:
The max length of the string I will pass is 40, and what I do is to generate a sequence of numbers from one to forty, extract the Nth character from the string where N is the sequence value but if the character is not a digit then I replace with a white space, then I return the XML as string enlcosing with <A>XXX</A>
to then convert to xml and then query that and return the first item order by it's length desc.
thanks,
While I'm not 100% sure how much better this would be with performance, here is an approach that breaks down the strings into any potential numeric combination and returns the first with the longest length:
DECLARE #foo TABLE(ID varchar(40));
INSERT #foo VALUES('P0123/99282'),('P9-123BB-12339'),('12345/54321');
;WITH NumbersTable AS
(
SELECT TOP (40) n = ROW_NUMBER() OVER (ORDER BY Number)
FROM master.dbo.spt_values
ORDER BY Number
), Results AS
(
SELECT f.Id, SUBSTRING(f.ID, t1.n, t2.n) numericvalues,
row_number() over (partition by f.Id
order by LEN(SUBSTRING(f.ID, t1.n, t2.n)) desc) rn
FROM NumbersTable t1
INNER JOIN #foo AS f
ON t1.n <= LEN(f.ID)
INNER JOIN NumbersTable t2
ON t2.n <= LEN(f.ID)
WHERE SUBSTRING(f.ID, t1.n, t2.n) NOT LIKE '%[^0-9]%'
)
SELECT *
FROM Results
WHERE rn = 1
This creates a numbers table from 1 to 40 (since that was your max length), and then using joins creates every substring variation of the data that have a numeric value using NOT LIKE '%[^0-9]%', and then establishes a row_number based on the len of that substring.
Fiddle Demo

Get the word before a semicolon from a string in SQL Server

I have the below string:
Consolidation CompletedThe Scenario is LDP; the Year is 2018; the Start Period is July; the End Period is June; the Entity is TOT_NEWS.
And need to get the words just before the semicolon (;) and the last word before the dot (.)
Result:
----- ---- ---- ---- ----
LDP 2018 July June TOT_NEWS
I could obtain the first one LDP with the below select:
REVERSE(LEFT(REVERSE(SUBSTRING(strDescription, CHARINDEX('The Scenario
is', strDescription,+39),
CHARINDEX(';',strDescription,-1))), CHARINDEX (' ',
REVERSE(SUBSTRING(strDescription,
CHARINDEX('The Scenario is', strDescription,+39),
CHARINDEX(';',strDescription,-1)))))) as Scenario
But it doesn´t work for the rest of the string.
Here's a way using a string splitter...
declare #value varchar(max) = 'Consolidation CompletedThe Scenario is LDP; the Year is 2018; the Start Period is July; the End Period is June; the Entity is TOT_NEWS.'
;with cte as(
select
* from
dbo.DelimitedSplit8K(#value,';'))
select
replace(right(Item,charindex(' ',reverse(Item),1)),'.','')
from cte
THE FUNCTION
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
Creator of the Function
If you are open to a TVF (Table-Valued Function).
This approach uses a modifed split/parse function. Rather than one delimeter, I use two non-like delimiters. It this case a ' ' and ';'
Example
Declare #YourTable table (id int,strDescription varchar(max))
Insert Into #YourTable values
(1,'Consolidation CompletedThe Scenario is LDP; the Year is 2018; the Start Period is July; the End Period is June; the Entity is TOT_NEWS.')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Pos1 = max(case when RetSeq=1 then RetVal end)
,Pos2 = max(case when RetSeq=2 then RetVal end)
,Pos3 = max(case when RetSeq=3 then RetVal end)
,Pos4 = max(case when RetSeq=4 then RetVal end)
,Pos5 = max(case when RetSeq=5 then RetVal end)
From [dbo].[udf-Str-Extract](A.strDescription+';',' ',';')
) B
Returns
ID Pos1 Pos2 Pos3 Pos4 Pos5
1 LDP 2018 July June TOT_NEWS
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[udf-Str-Extract] (#String,'[[',']]')
*/
Here you go:
-- Defining your string
DECLARE #string varchar(max)
SET #string = 'Consolidation CompletedThe Scenario is LDP; the Year is 2018; the Start Period is July; the End Period is June; the Entity is TOT_NEWS.'
;with pos as (
SELECT
#string as String
,CHARINDEX(';', #string, 1) as Pos_1
,CHARINDEX(';', #string, CHARINDEX(';', #string, 1)+1) as Pos_2
,CHARINDEX(';', #string, CHARINDEX(';', #string, CHARINDEX(';', #string, 1)+1)+1) as Pos_3
,CHARINDEX(';', #string, CHARINDEX(';', #string, CHARINDEX(';', #string, CHARINDEX(';', #string, 1)+1)+1)+1) as Pos_4
) , txt as (
select
String
,substring(String, 1,Pos_1-1) as String_1
,substring(String,Pos_1+1,Pos_2-Pos_1-1) as String_2
,substring(String,Pos_2+1,Pos_3-Pos_2-1) as String_3
,substring(String,Pos_3+1,Pos_4-Pos_3-1) as String_4
,substring(String,Pos_4+1,LEN(String)-Pos_4-1) as String_5
from pos
)
SELECT
string
,substring(String_1,len(String_1)-charindex(' ',reverse(String_1),1)+1,charindex(' ',reverse(String_1))) as Result_1
,substring(String_2,len(String_2)-charindex(' ',reverse(String_2),1)+1,charindex(' ',reverse(String_2))) as Result_2
,substring(String_3,len(String_3)-charindex(' ',reverse(String_3),1)+1,charindex(' ',reverse(String_3))) as Result_3
,substring(String_4,len(String_4)-charindex(' ',reverse(String_4),1)+1,charindex(' ',reverse(String_4))) as Result_4
,substring(String_5,len(String_5)-charindex(' ',reverse(String_5),1)+1,charindex(' ',reverse(String_5))) as Result_5
from txt
Output:
string | Result_1 | Result_2 | Result_3 | Result_4 | Result_5
----------------------------------------------------------------------------------------------------------------------------------------|-----------|----------|----------|----------|---------
Consolidation CompletedThe Scenario is LDP; the Year is 2018; the Start Period is July; the End Period is June; the Entity is TOT_NEWS. | LDP | 2018 | July | June | TOT_NEWS

CRON expression to next DateTime with T-SQL on SQL server

I have a column that contains a CRON expression that represents how often a user has to perform a task. I would like to build a view that lists the date and things to do for a given user. But I need to calculate my next CRON occurrence in a datetime in T-SQL. How interpreted my expression CRON in SQL?
example:
column value = [0 30 8 1 *?]
I would write:
SELECT CrontabSchedule ( '0 30 8 1 *?', GETDATE ()) FROM dbo.UserTasks
Someone has a solution ?
There is no built in function within SQL server to genrate Next date based on Cron expression. Only way would be, implemeting cron exprssions in C# and genrate it as dll,then regsiter DLL with SQL server CLR.
Refernces:
https://social.msdn.microsoft.com/Forums/en-US/3e0ebe5a-d452-4893-bcef-0a1e2520c076/convert-cron-expression-to-datetime?forum=transactsql
https://github.com/atifaziz/NCrontab/wiki/SQL-Server-Crontab
Maybe this link can give you a hint: https://ask.sqlservercentral.com/questions/117285/tsql-sql-function-for-a-cron-expression.html
The UDF presented in the link is as follow:
CREATE PROCEDURE Q117285.CreateJobScheduleFromCronExpression
#CronExpression varchar(200)
, #FieldDelimiterCharacter char(1) -- any whitespace character
AS
BEGIN;
DECLARE #CronExpressionArray TABLE
(
Seconds varchar(50)
, Minutes varchar(50)
, Hours varchar(50)
, DayOfMonth varchar(50)
, Month varchar(50)
, DayOfWeek varchar(50)
, Year varchar(50)
);
INSERT #CronExpressionArray
SELECT
[Seconds] = [1]
, [Minutes] = [2]
, [Hours] = [3]
, [DayOfMonth] = [4]
, [Month] = [5]
, [DayOfWeek] = [6]
, [Year] = [7]
FROM
(
SELECT ds.ItemNumber, ds.Item
FROM dbo.DelimitedSplit8K(#CronExpression, #FieldDelimiterCharacter) ds
) pSrc
PIVOT
(
MAX(pSrc.Item)
FOR pSrc.ItemNumber IN
([1], [2], [3], [4], [5], [6], [7])
) pvt
;
SELECT * FROM #CronExpressionArray;
END;
The DelimitedSplit8K UDF was extracted from the article: http://www.sqlservercentral.com/articles/Tally+Table/72993/
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
;
I needed to do something similar and eventually built my own tSQLcron
Currently it isn't SET based as it is a stored-procedure. An example usage is
DECLARE #out_is_cron_true BIT ;
EXEC tSQLcron.usp_is_date_in_cron_period
#cron_expression = N'* 0/15 * * * *' -- nvarchar(100)
, #validate_date = '2020-01-01 13:15:00' -- datetime
, #out_is_cron_true = #out_is_cron_true OUTPUT -- bit
IF (#out_is_cron_true = 1 )
BEGIN
PRINT 'DO SOMETHING';
END

Finding sequence of the last numeric value in a varchar variable

I have a column in a table which has incremented values like:
AAA0000001
AAA0000002
... and so on
I want to find if the values stored in this column are in proper sequential order or if any value is missing in between or is deleted.
How can i achieve this?
Assuming the pattern is always: AAA[0-9][0-9][0-9][0-9][0-9][0-9][0-9], you can do this with a Tally Table.
Sample Data:
CREATE TABLE Tbl(val VARCHAR(10))
INSERT INTO Tbl VALUES
('AAA0000001'), ('AAA0000002'), ('AAA0000004'), ('AAA0000011');
val
----------
AAA0000001
AAA0000002
AAA0000004
AAA0000011
SQL Fiddle
;WITH Cte AS(
SELECT *,
num = CAST(SUBSTRING(val, 4, LEN(val) - 3) AS INT)
FROM Tbl
),
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
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(SELECT MAX(num) FROM Cte)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
SELECT
N,
val = 'AAA' + RIGHT('0000000' + CAST(N AS VARCHAR(7)), 7)
FROM Tally
WHERE NOT EXISTS(
SELECT 1 FROM Cte WHERE num = N
)
RESULT
N val
-------------------- ----------
3 AAA0000003
5 AAA0000005
6 AAA0000006
7 AAA0000007
8 AAA0000008
9 AAA0000009
10 AAA0000010
Explanation:
The first CTE, named as Cte, extracts the numeric part of the strings and CASTs them to INT.
The succeeding CTEs, from E1 to Tally(N) generates a table with sequential values from 1 up to the MAX(num) - the INT return from the first CTE.
The final SELECT just checks for the non-existing num from the first CTE.
'AAA' + RIGHT('0000000' + CAST(N AS VARCHAR(7)), 7) transforms N so that it follows the pattern.
This is a Gaps problem. You can look into this article by Dwain Camps for more solutions on Gaps and Islands.
You can use ROW_NUMBER like this.
Sample Data
DECLARE #tab1 TABLE(id VARCHAR(20));
insert into #tab1 VALUES('AAA0000001'),('AAA0000002'),('AAA0000003'),('AAA0000004'),('AAA0000006'),('AAA0000007'),('AAA0000010');
Query
;WITH CTE as
(
SELECT convert(int,STUFF(id,1,3,'')) id,convert(int,STUFF(id,1,3,'')) - ROW_NUMBER()OVER(ORDER BY convert(int,STUFF(id,1,3,''))) rn
FROM #tab1
),CTE2 as
(
SELECT ROW_NUMBER()OVER(ORDER BY rn) as rn, MIN(id) series_start,MAX(id) series_end
FROM CTE
GROUP BY rn
)
SELECT C2.series_end,C1.series_start
FROM CTE2 C1
INNER JOIN CTE2 C2 ON C1.rn = C2.rn + 1;
SQL Fiddle
Explanation
Output of CTE is the difference of gaps between id values.
Output of CTE2 is the start and end of continuous series of numbers
Final Output gives the start and end of gaps within the series
Output
series_end series_start
4 6
7 10
If the schema is fixed then no need for complex queries. This works:
DECLARE #t TABLE ( v VARCHAR(100) );
INSERT INTO #t
VALUES ( 'AAA0000001' ),
( 'AAA0000002' ),
( 'AAA0000007' ),
( 'AAA0000008' ),
( 'AAA0000010' ),
( 'AAA0000011' ),
( 'AAA0000012' );
SELECT * FROM #t t1
CROSS APPLY(SELECT TOP 1 v FROM #t t2 WHERE t2.v > t1.v ORDER BY v) ca
WHERE RIGHT(t1.v, 7) <> RIGHT(ca.v, 7) - 1
Output:
v v
AAA0000002 AAA0000007
AAA0000008 AAA0000010
In sqlserver 2012, you can use LAG and LEAD
DECLARE #t table(col1 varchar(15))
INSERT #t values('AAA0000001'),('AAA0000002'),('AAA0000004')
SELECT
case when
stuff(lag(col1) over (order by col1), 1,3,'') + 1
= stuff(col1, 1,3,'') then 'Yes' else 'No' end previous_exists,
case when
stuff(lead(col1) over (order by col1), 1,3,'') - 1
= stuff(col1, 1,3,'') then 'Yes' else 'No' end next_exists,
col1
FROM #t
Result:
previous_exists next_exists col1
No Yes AAA0000001
Yes No AAA0000002
No No AAA0000004

Resources