I have the following table column named file and it contains directories.
Sample data is:
C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML
C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML
Note that I only want to get 6860_f11.xlxb for #1 and 1191_f12.xlxb for #2.
For #1, the directory only contains 1 folder filedata but for #2 it contains 2 folders cloud\files
Below is my code:
select
(SUBSTRING((file), 0, CHARINDEX ('.xlxb', (file)) + 4)) as xlsb_file
from
[Projects].[dbo].[ProjFiles]
Is there any way I could get the string after the folder and until the underscore after .xlxb?
No CLR needed. No Regex required. The easiest and best performing way to solve this using NGrams8K. It's 2AM where I live so I'll make this quick.
Note this query:
DECLARE #string VARCHAR(150) = 'C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML';
SELECT RetPos = f.p, RetVal = e.s
FROM (SELECT MAX(position)+1 FROM samd.NGrams8k(#string,1) WHERE token = '\') AS f(p)
CROSS APPLY (VALUES(SUBSTRING(#string,f.p,CHARINDEX('.',#string,f.p)-f.p+5))) AS e(s);
Results:
RetPos RetVal
------ ---------------
16 1191_f12.xlxb
Now Against a table:
CREATE TABLE #yourtable ([file] VARCHAR(150));
INSERT INTO #yourtable
VALUES ('C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML'),
('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML');
SELECT *
FROM #yourtable AS t
CROSS APPLY
(
SELECT newstring = e.s
FROM (SELECT MAX(position) FROM samd.NGrams8k(t.[file],1) WHERE token = '\') AS f(p)
CROSS
APPLY (VALUES(SUBSTRING(t.[file],f.p+1,CHARINDEX('.',t.[file],f.p+1)-f.p+4))) AS e(s)
) AS itvf_str_extract;
It's really that easy. The performance will beats the pants of any CLR/Regex Based solution too - so there's that.
On a side note: John Cappelletti's solution is excellent (as per usual). Under the hood it's very similar to my NGrams solution but not exactly; compare these two queries:
DECLARE #string VARCHAR(150) = 'C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML';
DECLARE #Delimiter1 varchar(100) = '\', #Delimiter2 varchar(100) = '.';
-- Alan B
SELECT
RetSeq = 1,
RetPos = f.p,
RetVal = e.s
FROM (SELECT MAX(position)+1 FROM samd.NGrams8k(#string,1) WHERE token = '\') AS f(p)
CROSS
APPLY (VALUES(SUBSTRING(#string,f.p,CHARINDEX('.',#string,f.p)-f.p+5))) AS e(s);
-- John C
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;
Now the execution plans:
There are several ways to do it. This is one method
select substring(reverse(substring(reverse('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML'),1,charindex('\',reverse('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML'))-1)),1,13)
And if your filename is not always the same, you could do the same logic with _
This one will work with underscore - given that there is no other underscore than xlxb_
select
reverse(substring(reverse(reverse(substring(reverse('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML'),1,charindex('\',reverse('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML'))-1))),charindex('_',reverse('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML'))+1,len('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML')))
For such tasks it will be better to implement SQL CLR function. Here is example of how to create such. Having regex support, you can do things like the following:
DECLARE #DataSource TABLE
(
[value] VARCHAR(256)
);
INSERT INTO #DataSource ([value])
VALUES ('C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML')
,('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML');
SELECT *
FROM #DataSource DS
CROSS APPLY [dbo].[fn_Utils_RegexMatches] ([value], '[^\\]+\.xlxb');
Of course, you can create more complicate patterns to match using regular expressions.
If open to a Table Valued Function, consider the following.
Tired of extracting strings (left,right,charindex,patindex,etc.), I modified a parse function to accept two non-like delimiters. In this case '\' and '.xlxb'
Also being a TVF, it is easy to incorporate into a CROSS APPLY.
Example
Declare #YourTable table ([file] varchar(150))
Insert Into #YourTable values
('C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML')
,('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML')
Select A.*
,B.*
From #YourTable A
Cross Apply [dbo].[tvf-Str-Extract](A.[File],'\','.xlxb') B
Returns
file RetSeq RetPos RetVal
C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML 1 13 6860_f11
C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML 1 16 1191_f12
The TVF if Interested
CREATE FUNCTION [dbo].[tvf-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].[tvf-Str-Extract] (#String,'[[',']]')
*/
For SQL Server, starting with 2016, you may use STRING_SPLIT():
CREATE TABLE #FileTable (
FullName varchar(150)
)
INSERT INTO #FileTable (FullName)
VALUES
('C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML'),
('C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML')
SELECT
f.FullName,
SUBSTRING(s.[value], 1, CHARINDEX('.xlxb', s.[value]) + LEN('xlxb')) AS FileName
FROM #FileTable f
CROSS APPLY STRING_SPLIT(f.FullName, '\') s
WHERE s.[value] LIKE '%.xlxb%'
Output:
FullName FileName
C:\filedata\6860_f11.xlxb_3.30 test - 0.3 ML 6860_f11.xlxb
C:\cloud\files\1191_f12.xlxb_12.16 test - 0.3 ML 1191_f12.xlxb
Related
The string is in
#txt nvarchar(max)='2450,10,54,kb2344,kd5433;87766,500,100,ki5332108,ow092827'
And I want output like this:
Id. Val1. Val2. Val3. Val4. Val5.
1. 2450 10 54 kb2344 kd5433.
2. 87766 500 100 ki5332108 ow09287
Can anybody suggest how to do that?
I google it , and found this solution. But it is for two comma separated values but in my case there are five:
DECLARE #Var NVARCHAR(100) = '2450,10,54,kb2344,kd5433;87766,500,100,ki5332108,ow092827'
SELECT LEFT(#Var, CHARINDEX(';', #Var) - 1) ,SUBSTRING(#Var, CHARINDEX(';', #Var) + 1, LEN(#Var)- LEN(LEFT(#Var, CHARINDEX(';', #Var)))- LEN(RIGHT(#Var, CHARINDEX(';', REVERSE(#Var))))) AS [Job] , RIGHT(#Var, CHARINDEX(';', REVERSE(#Var))-1)
Assuming values 1-5. This can easily be done with a little XML in concert with a CROSS APPLY
If the number columns are variable, you would have to go DYNAMIC.
EDIT - Changed to nvarchar
Example
Declare #txt nvarchar(max)='2450,10,54,kb2344,kd5433;87766,500,100,ki5332108,ow092827'
Select ID=A.RetSeq
,B.*
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'nvarchar(max)')))
From (Select x = Cast('<x>' + replace(#txt ,';','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) A
Cross Apply (
Select Val1 = ltrim(rtrim(xDim.value('/x[1]','nvarchar(max)')))
,Val2 = ltrim(rtrim(xDim.value('/x[2]','nvarchar(max)')))
,Val3 = ltrim(rtrim(xDim.value('/x[3]','nvarchar(max)')))
,Val4 = ltrim(rtrim(xDim.value('/x[4]','nvarchar(max)')))
,Val5 = ltrim(rtrim(xDim.value('/x[5]','nvarchar(max)')))
From (Select Cast('<x>' + replace(A.RetVal,',','</x><x>')+'</x>' as xml) as xDim) as B1
) B
Returns
ID Val1 Val2 Val3 Val4 Val5
1 2450 10 54 kb2344 kd5433
2 87766 500 100 ki5332108 ow092827
I have a Description column that contains a long string with varying length and want to extract the content found between every brackets in that string.
I am able to extract the content found in the first pair of brackets but not sure how to tackle cases where more pairs of brackets are found.
I would like to use a SELECT statement only if possible.
My query so far looks like this
SELECT SUBSTRING (Description, CHARINDEX('[', Description)+1, CHARINDEX(']', Description)-CHARINDEX('[', Description)-1)
FROM [MyTable].[Description]
WHERE Description like '%(%'
So for example with the following data
Description (column)
Row 1 blablablablalbaalala (blibliblobloblo) blalblalala (blululublululu)
My query will only return
'blibliblobloblo' but I also want 'blululublululu'
SQL Server 2016 and above
DECLARE #foo varchar(100) = 'lablablablalbaalala (blibliblobloblo) blalblalala (blululublululu)'
SELECT
LEFT(value, CHARINDEX(')', value)-1)
FROM
STRING_SPLIT(#foo, '(')
WHERE
value LIKE '%)%'
If you are open to a TVF (Table-Valued Function).
Tired of extracting strings (charindex,left,right,...) I modified a parse function to accept two non-like delimiters. In your case this would be ( and ).
Example
Declare #YourTable table (ID int,SomeCol varchar(max))
Insert Into #YourTable values
(1,'blablablablalbaalala (blibliblobloblo) blalblalala (blululublululu)')
Select A.ID
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Str-Extract](A.SomeCol,'(',')') B
Returns
ID RetSeq RetPos RetVal
1 1 23 blibliblobloblo
1 2 53 blululublululu
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,'[[',']]')
*/
I am working with an employee hierarchy string that is in the format of the following. These number represent employeeID numbers and how the are structured within the company, thus being able to follow the chain of management.
123|456|789|012|345|320
I am trying to take this string of data and turn it into a temp table so I can work with each of the ID's as their own value.
I tried making a function to split the string:
ALTER FUNCTION [dbo].[SplitString]
(#String NVARCHAR(4000),
#Delimiter NCHAR(1))
RETURNS TABLE
AS
RETURN
(WITH Split(stpos, endpos) AS
(
SELECT 0 AS stpos, CHARINDEX(#Delimiter, #String) AS endpos
UNION ALL
SELECT endpos + 1, CHARINDEX(#Delimiter, #String, endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT
'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String, stpos, COALESCE(NULLIF(endpos, 0), LEN(#String) + 1))
FROM
Split
)
This however resulted in the following:
Id Data
-------------------
1 123
2 456|7893
3 7893|012|345|
4 012|345|320
5 345|320
6 320
Is there a better way to approach this, maybe not needing a function at all or will it be required to achieve this?
Without a Parse Function
Declare #YourTable table (ID int,IDList varchar(Max))
Insert Into #YourTable values
(1,'123|456|789|012|345|320'),
(2,'123|456')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
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 A.IDList as [*] For XML Path('')),'|','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Returns
ID RetSeq RetVal
1 1 123
1 2 456
1 3 789
1 4 012
1 5 345
1 6 320
2 1 123
2 2 456
OR with the SUPER DUPER Parse (orig source listed below / couple of tweaks)
Select A.ID
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse-8K](A.IDList,'|') B
Would Return the same as above
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(10))
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 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = Substring(#String, A.N, A.L)
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
Edit - Stand Alone
Declare #String varchar(max) = '123|456|789|012|345|320'
Declare #Delim varchar(10) = '|'
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 #String as [*] For XML Path('')),#Delim,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
If you need a string "splitter" the fastest one available for 2012 (pre- 2016) is going to be found here. This will blow the doors off of anything posted thusfar. If your items/tokens are all the same size then an even faster method would be this:
DECLARE #yourstring varchar(8000) = '123|456|789|012|345|320';
WITH E(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(v)),
iTally(N) AS (SELECT TOP ((LEN(#yourstring)/4)+1) ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM e a, e b, e c, e d)
SELECT itemNumber = ROW_NUMBER() OVER (ORDER BY N), item = SUBSTRING(#yourstring, ((N*4)-3), 3)
FROM iTally;
Results:
itemNumber item
-------------------- ----
1 123
2 456
3 789
4 012
5 345
6 320
I write more about this and provide examples of how to put this logic into a function here.
I use this version of the Split function.
CREATE FUNCTION [dbo].[Split]
(
#delimited nvarchar(max),
#delimiter nvarchar(100)
) RETURNS #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id int identity(1,1), -- I use this column for numbering splitted parts
val nvarchar(max)
)
AS
BEGIN
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(max)') as item
from #xml.nodes('//root/r') as records(r)
RETURN
END
You Query would look something like....
SELECT *
FROM TableName t
CROSS APPLY [dbo].[Split](t.EmpIDs, '|')
A table has a filed with type varchar(50)
this filed contains number,string and mix like 1001,10012,1001b
I want to write q query that sort it character by character :
1001
1001b
10012
You can use a pattern splitter to split your string into number and characters. Here is PatternSplitCM taken from Dwain Camp's article:
-- PatternSplitCM will split a string based on a pattern of the form
-- supported by LIKE and PATINDEX
--
-- Created by: Chris Morris 12-Oct-2012
CREATE FUNCTION [dbo].[PatternSplitCM]
(
#List VARCHAR(8000) = NULL
,#Pattern VARCHAR(50)
) RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH numbers AS (
SELECT TOP(ISNULL(DATALENGTH(#List), 0))
n = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) g (n)
)
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY MIN(n)),
Item = SUBSTRING(#List,MIN(n),1+MAX(n)-MIN(n)),
[Matched]
FROM (
SELECT n, y.[Matched], Grouper = n - ROW_NUMBER() OVER(ORDER BY y.[Matched],n)
FROM numbers
CROSS APPLY (
SELECT [Matched] = CASE WHEN SUBSTRING(#List,n,1) LIKE #Pattern THEN 1 ELSE 0 END
) y
) d
GROUP BY [Matched], Grouper
Your final query would be:
;WITH tbl(string) AS(
SELECT '10001' UNION ALL
SELECT '100012' UNION ALL
SELECT '10001b' UNION ALL
SELECT 'b1000'
)
SELECT
t.string,
NumPart = MAX(CASE WHEN s.Matched = 0 THEN CAST(s.Item AS INT) END),
CharPart = MAX(CASE WHEN s.Matched = 1 THEN s.Item END)
FROM tbl t
CROSS APPLY dbo.PatternSplitCM(string, '%[^0-9]%') s
GROUP BY t.string
ORDER BY
NumPart, CharPart
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