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
Related
I have following order detail query
OrderId item Quantity
----------------------------------
3402323 Item1 3
3402323 Item2 1
3402324 Item1 2
And the results I need are
OrderId item Quantity
----------------------------------
3402323-1 Item1 1
3402323-2 Item1 1
3402323-3 Item1 1
3402323-4 Item2 1
3402324-1 Item1 1
3402324-2 Item1 1
Is there a way to do this without using a temp table and populating it with a cursor?
I would personally use a Tally. These are far faster than a rCTE, especially if (in this scenario) you have large values for Quantity, and can't suffer from hitting the max recursion problem, as they aren't recursive.
WITH YourTable AS(
SELECT *
FROM (VALUES(3402323,'Item1',3),
(3402323,'Item2',1),
(3402324,'Item1',2))V(OrderID,Item,Quantity)),
N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT MAX(Quantity) FROM YourTable)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3) --1000 rows
SELECT CONCAT(YT.OrderID,'-',T.I) AS OrderID,
YT.Item,
1 AS Quantity
FROM YourTable YT
JOIN Tally T ON YT.Quantity >= T.I;
Use a recursive subquery:
with cte as (
select orderid, item, quantity, 1 as n
from t
union all
select orderid, item, quantity, n + 1
from cte
where n < quantity
)
select orderid, item, 1 as quantity
from cte;
If your quantities exceed 100, then you need to add option (maxrecursion 0).
If you have SQL Server 2016 or later you can utilize the STRING_SPLIT function and the REPLICATE function to achieve the result you are looking for. The REPLICATE function can be used to create a comma delimited string that repeats N number of times (this will be your quantity). The STRING_SPLIT function can be used to split the string into N number of records you can join on.
The following example shows how this would work with your data.
DECLARE #orders TABLE
(
OrderId VARCHAR(50)
,Item VARCHAR(50)
,Quantity INT
)
INSERT INTO #orders
SELECT 3402323 ,'Item1', 3 UNION ALL
SELECT 3402323 ,'Item2', 1 UNION ALL
SELECT 3402324 ,'Item1', 2
SELECT
OrderId = O.OrderId + '-' + CAST( (ROW_NUMBER() OVER (PARTITION BY O.OrderId ORDER BY O.Item, OE.RepeatId) ) AS VARCHAR(50) )
,O.Item
,Quantity = 1
FROM
#orders O
CROSS APPLY
(
SELECT RepeatId = ROW_NUMBER() OVER (ORDER BY [value])
FROM
STRING_SPLIT(REPLICATE ( ',', O.Quantity - 1), ',')
) OE
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:
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
In a table there are three columns, each containing numeric values (in my case representing length, width and height, but could be anything).
How can I select them, ordered by their value?
For example, given the values:
id | length | width | height
1 | 100 | 30 | 50
2 | 6 | 12 | 9
Expected output would be:
id | min | mid | max
1 | 30 | 50 | 100
2 | 6 | 9 | 12
Use the Table Value Constructor with ORDER BY to get the relevent values in sorted order.
Using OFFSET X ROWS and FETCH NEXT 1 ROWS ONLY, you can access a specific position inside the sorted column values.
Use it repeatedly (increasing OFFSET by 1 in each step) to access the sorted column values at each position.
SELECT
length,
width,
height,
(
SELECT dimensions
FROM ( VALUES (length),(width),(height) ) AS compare(dimensions)
ORDER BY dimensions ASC
OFFSET 0 ROWS
FETCH NEXT 1 ROWS ONLY
) AS minDimension,
(
SELECT dimensions
FROM ( VALUES (length),(width),(height) ) AS compare(dimensions)
ORDER BY dimensions ASC
OFFSET 1 ROWS
FETCH NEXT 1 ROWS ONLY
) AS midDimension,
(
SELECT dimensions
FROM ( VALUES (length),(width),(height) ) AS compare(dimensions)
ORDER BY dimensions ASC
OFFSET 2 ROWS
FETCH NEXT 1 ROWS ONLY
) AS maxDimension
FROM sometable
Try this
DECLARE #T TABLE
(
Id INT,
[Length] INT,
Width INT,
Height INT
)
INSERT INTO #T
VALUES(1,100,30,50),(2,6,9,12)
;WITH CTE
AS
(
SELECT
Id,
Val = [Length]
FROM #T
UNION ALL
SELECT
Id,
Val = Width
FROM #T
UNION ALL
SELECT
Id,
Val = Height
FROM #T
),C2
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Val),
Id,
Val
FROM CTE
)
SELECT
Id,
[1] "min",
[2] "mid",
[3] "max"
FROM C2
PIVOT
(
MAX(VAL)
FOR
SeqNo IN
(
[1],[2],[3]
)
)q
One alternate solution, using PERCENTILE_CONT to obtain the Median:
WITH Measurement AS(
SELECT *
FROM (VALUES (1,100,30,50),
(2,6,12,9)) V(ID, [Length], Width, Height)),
Pvt AS(
SELECT ID,
D.Measurement AS MeasurementType,
CASE WHEN D.Measurement = 'Length' THEN M.[Length]
WHEN D.Measurement = 'Width' THEN M.Width
WHEN D.Measurement = 'Height' THEN M.Height
END AS Measurement
FROM Measurement M
CROSS APPLY (VALUES ('Length'),('Width'),('Height')) D(Measurement)),
Median AS(
SELECT ID,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Measurement) OVER (PARTITION BY ID) AS Median,
MeasurementType, Measurement
FROM Pvt)
SELECT ID,
MIN(Measurement) AS [Min],
Median,
MAX(Measurement) AS [Max]
FROM Median
GROUP BY ID, Median
ORDER BY ID;
First, make the values of columns length, width and height to one column using union. And then use this result-set as a sub-query and give a row number based on group by id and descending order of the newly added column. Then by using a CASE expression take the row having row number 1 as max, 2 as mid and 3 as min.
Query
;with cte as(
select [rn] = row_number() over(
partition by t.[id]
order by t.[new_col] desc
), *
from (
select [id], [length] as [new_col] from [your_table_name]
union all
select [id], [width] from [your_table_name]
union all
select [id], [height] from [your_table_name]
) t
)
select [id],
max(case [rn] when 1 then [new_col] end) as [max],
max(case [rn] when 2 then [new_col] end) as [mid],
max(case [rn] when 3 then [new_col] end) as [min]
from cte
group by [id];
Fiddle demo
This seems to work:
Query
declare #table table (id integer, length integer, width integer, height integer)
insert into #table select 1,100,30,50
insert into #table select 2,6,12,9
select id, MIN(a) min
,(select top 1 max(c.a) from
(
select id,length a from #table
union all
select id,width a from #table
union all
select id,height a from #table
)c
where c.id = b.id and c.a <> max(b.a) and c.a <> min(b.a) group by c.id) mid, max(a) max
from
(
select id,length a from #table
union all
select id,width a from #table
union all
select id,height a from #table
)b
group by id
i have a piece of code looks like this
declare #t table (record int,string varchar(MAX))
insert into #t (record,string)values (1,'ABC')
insert into #t (record,string)values (2,'DEF/123')
insert into #t (record,string)values (3,'GHI/456/XYZ')
i got a query where i can result like this
SELECT record,
RIGHT(LEFT(T.string,Number-1),CHARINDEX('/',REVERSE(LEFT('/' + T.string,number-1))))
FROM
master..spt_values,
#t T
WHERE
Type = 'P' AND Number BETWEEN 1 AND LEN(T.string)+1
AND
(SUBSTRING(T.string,Number,1) = '/' OR SUBSTRING(T.string,Number,1) = '')
getting output
record values
1 ABC
2 DEF
2 123
3 GHI
3 456
3 XYZ
how can i get output like this
record values
1 ABC
1 NULL
1 NULL
2 DEF
2 123
2 NULL
3 GHI
3 456
3 XYZ
it has been asked by some user .i excelled upto here and from there how can i achieve desire output
The idea is to generate a rows of record cross joined to 1,2,3 to produce combination of record with another column numbered 1,2,3, then use that combination to join to your splitted values. You must add a ROW_NUMBER for your splitted values first to be able to join it with the generated combinations.
;WITH CteThree(record, N) AS(
SELECT
t.record,
x.N
FROM (
SELECT DISTINCT record FROM #t
)t
CROSS JOIN(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
)x(N)
),
CteSplitted AS(
SELECT
record,
ROW_NUMBER() OVER(PARTITION BY record ORDER BY Number) AS N,
RIGHT(LEFT(T.string,Number-1),CHARINDEX('/',REVERSE(LEFT('/' + T.string,number-1)))) AS str
FROM master..spt_values v
CROSS JOIN #t T
WHERE
Type = 'P'
AND Number BETWEEN 1 AND LEN(T.string)+1
AND (SUBSTRING(T.string,Number,1) = '/' OR SUBSTRING(T.string,Number,1) = '')
)
SELECT
t.record,
s.str
FROM CteThree t
LEFT JOIN CteSplitted s
ON s.record = t.record
AND s.N = t.N
how about this:
declare #t table (record int,string varchar(MAX));
declare #s char(1) = '/';
WITH counter as (
SELECT MAX(LEN(string)-LEN(REPLACE(string, #s, ''))) lines
) ,
splitter as (
SELECT record, string
, line = 1
, pos = h.pos
, value = CASE WHEN h.pos>0 THEN SUBSTRING(string,1,h.pos) ELSE string END
FROM #t
CROSS APPLY (SELECT CHARINDEX(#s, string) pos ) h
UNION ALL
SELECT record, string
, line = s.line + 1
, pos = CASE WHEN s.pos = 0 THEN 0 ELSE h.pos END
, value = CASE WHEN s.pos = 0 THEN null
WHEN h.pos > 0 THEN SUBSTRING(string,s.pos+1,h.pos-s.pos-1)
ELSE SUBSTRING(string,s.pos+1,99)
END
FROM splitter s
CROSS APPLY (SELECT CHARINDEX(#s, string, s.pos+1) pos ) h
WHERE s.line<=(SELECT lines FROM counter)
)
SELECT *
FROM splitter
ORDER BY record,line
try this
DECLARE #t TABLE
(
record INT ,
string VARCHAR(MAX)
)
INSERT INTO #t
( record, string )
VALUES ( 1, 'ABC' ),
( 2, 'DEF/123' ),
( 3, 'GHI/456/XYZ' );
WITH cte
AS ( SELECT Number = 1
UNION ALL
SELECT Number + 1
FROM cte
WHERE Number <= 100
),
NotNull
AS ( SELECT record ,
RIGHT(LEFT(T.string, Number - 1),
CHARINDEX('/',
REVERSE(LEFT('/' + T.string,
number - 1)))) string ,
ROW_NUMBER() OVER ( PARTITION BY T.record ORDER BY T.record ) AS RN
FROM cte
JOIN #t T ON Number <= ( LEN(T.string) + 1 )
AND SUBSTRING(T.string + '/', Number, 1) = '/'
)
SELECT template.record ,
NotNull.string
FROM ( SELECT *
FROM ( SELECT DISTINCT
RN
FROM NotNull
) AS A
CROSS JOIN ( SELECT Record
FROM NotNull
) AS B
) AS template
LEFT JOIN NotNull ON template.RN = NotNull.RN
AND template.Record = NotNull.Record
try this
declare #t table (record int,string varchar(MAX))
insert into #t (record,string)values (1,'ABC')
insert into #t (record,string)values (2,'DEF/123')
insert into #t (record,string)values (3,'GHI/456/XYZ')
declare #mx int
select #mx= len(string)-len(replace(string,'/','')) from #t
select record,t.c.value('.','varchar(max)') as col2 from
(select record,x=cast('<t>'+replace(left(string+'////////////////////',(len(string)+(#mx-(len(string)-len(replace(string,'/','')))))),'/','</t><t>') +'</t>' as xml) from #t)
a cross apply x.nodes('/t') t(c)