Row By Row ( Without Cursor or Loops) - sql

Here I have sample data of students having RollNumbers and their coursecodes.
-------------------------
Roll CourseCode
--------------------------
1011 CS201
2213 CS201
3312 CS101
4000 CS201
1011 CS101
5312 ME102
1011 PT101
3312 ME102
Result should be Coursecode and their exam date
e.g (Sort Out Distinct Coursecodes)
First I am picking CS201 and assigning that coursecode a date; placing it in a temporary table,then I picked CS101 and will check in temporary table whether the RollNumbers of this Coursecode matches any other RollNumber of other Coursecode in the temporary table.
---------------------
Code Date
---------------------
CS101 1
CS201 2
ME102 1
PT101 3
My code:
#temp3 contains all data (CourseCodes, RollNumbers)
#mytemp1 ( Output Data)
and cursor contains the Distinct coursecodes
SET #cursor = CURSOR FOR
SELECT DISTINCT coursecode
FROM #temp3
ORDER BY CourseCode
OPEN #cursor
FETCH NEXT
FROM #cursor INTO #cursorid
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN
SET #j=1
WHILE(#j !=9999999)
BEGIN
IF( SELECT COUNT(*) FROM #temp3 WHERE CourseCode = #cursorid AND RegdNo IN (
SELECT RegdNo FROM #temp3 WHERE CourseCode IN ( SELECT coursecode FROM #myTemp1 WHERE counter1 = #j)
)) = 0
BEGIN
INSERT INTO #myTemp1 VALUES (#cursorid,#j)
SET #j=9999999
END
ELSE
BEGIN
SET #j = #j + 1
END
END
END
FETCH NEXT
FROM #cursor INTO #cursorid
END
CLOSE #cursor
DEALLOCATE #cursor
This code is working fine but taking too much time( 4110222 records)
Any help would be appreciated

Here is some code. I believe that you have error in output and CS101 should precede CS201:
DECLARE #t TABLE ( Roll INT, Code CHAR(5) )
INSERT INTO #t
VALUES ( 1011, 'CS201' ),
( 2213, 'CS201' ),
( 3312, 'CS101' ),
( 4000, 'CS201' ),
( 1011, 'CS101' ),
( 5312, 'ME102' ),
( 1011, 'PT101' ),
( 3319, 'ME102' );
WITH cte1
AS ( SELECT code ,
ROW_NUMBER() OVER ( ORDER BY Code ) AS rn
FROM #t
GROUP BY code
),
cte2
AS ( SELECT code ,
rn ,
1 AS Date
FROM cte1
WHERE rn = 1
UNION ALL
SELECT c1.code ,
c1.rn ,
CASE WHEN EXISTS ( SELECT *
FROM #t a
JOIN #t b ON a.Roll = b.Roll
JOIN cte1 c ON c.rn < c1.rn
AND b.Code = c.code
WHERE a.code = c1.code ) THEN 1
ELSE 0
END
FROM cte1 c1
JOIN cte2 c2 ON c1.rn = c2.rn + 1
),
cte3
AS ( SELECT Code ,
CASE WHEN Date = 0 THEN 1
ELSE SUM(Date) OVER ( ORDER BY rn )
END AS Date
FROM cte2
)
SELECT * FROM cte3
Output:
Code Date
CS101 1
CS201 2
ME102 1
PT101 3
EDIT:
cte1 will return:
code rn
CS101 1
CS201 2
ME102 3
PT101 4
The main work is done in cte2. It is recursive common table expression.
First you take top 1 row from cte1:
SELECT code ,
rn ,
1 AS Date
FROM cte1
WHERE rn = 1
Then the recursion progresses:
You are joining cte1 on cte2 and pick following rns (2, 3...) and check if there are any rolls in CS201 that match rolls in previous codes(CS101) in first step, check if there any rolls in ME102 that match rolls in previous codes(CS101, CS201) in second step etc. If exists you return 1 else 0:
code rn Date
CS101 1 1
CS201 2 1
ME102 3 0
PT101 4 1
Last cte3 does the following: if Date = 0 then return 1, else return sum of Dates in previous rows including current row.
EDIT1:
Since my understanding was incorrect here is one more statement:
WITH cte
AS ( SELECT code ,
ROW_NUMBER() OVER ( ORDER BY Code ) AS rn
FROM #t
GROUP BY code
)
SELECT co.Code,
DENSE_RANK() OVER(ORDER BY ISNULL(o.Code, co.Code)) AS Date
FROM cte co
OUTER APPLY(SELECT TOP 1 ci.Code
FROM cte ci
WHERE ci.rn < co.rn AND
NOT EXISTS(SELECT * FROM #t
WHERE code = ci.code AND
roll IN(SELECT roll FROM #t WHERE code = co.code)) ORDER BY ci.rn) o
ORDER BY co.rn
Output:
Code Date
CS101 1
CS201 2
ME102 1
PT101 2
EDIT2:
This is insane but, here is code that seems to work:
WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( PARTITION BY roll ORDER BY Code ) AS Date
FROM #t
)
SELECT Code ,
MAX(Date) AS Date
FROM cte
GROUP BY Code

Related

SQL Server 2008 filling gaps with dimension

I have a data table as below
#data
---------------
Account AccountType
---------------
1 2
2 0
3 5
4 2
5 1
6 5
AccountType 2 is headers and 5 is totals. Meaning accounts of type 2 have to look after the next 1 or 0 to determin if its Dim value is 1 or 0. Totals of type 5 have to look up at nearest 1 or 0 to determin its Dim value. Accounts of type 1 or 0 have there type as Dim.
Accounts of type 2 appear as islands so its not enough to just check RowNumber + 1 and same goes for accounsts of type 5.
I have arrived at the following table using CTE's. But can't find a quick way to go from here to my final result of Account, AccountType, Dim for all accounts
T3
-------------------
StartRow EndRow AccountType Dim
-------------------
1 1 2 0
2 2 0 0
3 3 5 0
4 4 2 1
5 5 0 1
6 6 5 1
Below code is MS TSQL copy paste it all and see it run. The final join on the CTE select statement is extremly slow for even 500 rows it takes 30 sec. I have 100.000 rows i need to handle. I done a cursor based solution which do it in 10-20 sec thats workable and a fast recursive CTE solution that do it in 5 sec for 100.000 rows, but it dependent on the fragmentation of the #data table. I should add this is simplified the real problem have alot more dimension that need to be taking into account. But it will work the same for this simple problem.
Anyway is there a fast way to do this using joins or another set based solution.
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#data') IS NOT NULL
DROP TABLE #data
CREATE TABLE #data
(
Account INTEGER IDENTITY(1,1),
AccountType INTEGER,
)
BEGIN -- TEST DATA
DECLARE #Counter INTEGER = 0
DECLARE #MaxDataRows INTEGER = 50 -- Change here to check performance
DECLARE #Type INTEGER
WHILE(#Counter < #MaxDataRows)
BEGIN
SET #Type = CASE
WHEN #Counter % 10 < 3 THEN 2
WHEN #Counter % 10 >= 8 THEN 5
WHEN #Counter % 10 >= 3 THEN (CASE WHEN #Counter < #MaxDataRows / 2.0 THEN 0 ELSE 1 END )
ELSE 0
END
INSERT INTO #data VALUES(#Type)
SET #Counter = #Counter + 1
END
END -- TEST DATA END
;WITH groupIds_cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY AccountType ORDER BY Account) - Account AS GroupId
FROM #data
),
islandRanges_cte AS
(
SELECT
MIN(Account) AS StartRow,
MAX(Account) AS EndRow,
AccountType
FROM groupIds_cte
GROUP BY GroupId,AccountType
),
T3 AS
(
SELECT I.*, J.AccountType AS Dim
FROM islandRanges_cte I
INNER JOIN islandRanges_cte J
ON (I.EndRow + 1 = J.StartRow AND I.AccountType = 2)
UNION ALL
SELECT I.*, J.AccountType AS Dim
FROM islandRanges_cte I
INNER JOIN islandRanges_cte J
ON (I.StartRow - 1 = J.EndRow AND I.AccountType = 5)
UNION ALL
SELECT *, AccountType AS Dim
FROM islandRanges_cte
WHERE AccountType = 0 OR AccountType = 1
),
T4 AS
(
SELECT Account, Dim
FROM (
SELECT FlattenRow AS Account, StartRow, EndRow, Dim
FROM T3 I
CROSS APPLY (VALUES(StartRow),(EndRow)) newValues (FlattenRow)
) T
)
--SELECT * FROM T3 ORDER BY StartRow
--SELECT * FROM T4 ORDER BY Account
-- Final correct result but very very slow
SELECT D.Account, D.AccountType, I.Dim FROM T3 I
INNER JOIN #data D
ON D.Account BETWEEN I.StartRow AND I.EndRow
ORDER BY Account
EDIT with some time testing
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#data') IS NULL
CREATE TABLE #times
(
RecId INTEGER IDENTITY(1,1),
Batch INTEGER,
Method NVARCHAR(255),
MethodDescription NVARCHAR(255),
RunTime INTEGER
)
IF OBJECT_ID('tempdb..#batch') IS NULL
CREATE TABLE #batch
(
Batch INTEGER IDENTITY(1,1),
Bit BIT
)
INSERT INTO #batch VALUES(0)
IF OBJECT_ID('tempdb..#data') IS NOT NULL
DROP TABLE #data
CREATE TABLE #data
(
Account INTEGER
)
CREATE NONCLUSTERED INDEX data_account_index ON #data (Account)
IF OBJECT_ID('tempdb..#islands') IS NOT NULL
DROP TABLE #islands
CREATE TABLE #islands
(
AccountFrom INTEGER ,
AccountTo INTEGER,
Dim INTEGER,
)
CREATE NONCLUSTERED INDEX islands_from_index ON #islands (AccountFrom, AccountTo, Dim)
BEGIN -- TEST DATA
INSERT INTO #data
SELECT TOP 100000 ROW_NUMBER() OVER(ORDER BY t1.number) AS N
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
INSERT INTO #islands
SELECT MIN(Account) AS Start, MAX(Account), Grp
FROM (SELECT *, NTILE(10) OVER (ORDER BY Account) AS Grp FROM #data) T
GROUP BY Grp ORDER BY Start
END -- TEST DATA END
--SELECT * FROM #data
--SELECT * FROM #islands
--PRINT CONVERT(varchar(20),DATEDIFF(MS,#RunDate,GETDATE()))+' ms Sub Query'
DECLARE #RunDate datetime
SET #RunDate=GETDATE()
SELECT Account, (SELECT Dim From #islands WHERE Account BETWEEN AccountFrom AND AccountTo) AS Dim
FROM #data
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'subquery','',DATEDIFF(MS,#RunDate,GETDATE()))
SET #RunDate=GETDATE()
SELECT D.Account, V.Dim
FROM #data D
CROSS APPLY
(
SELECT Dim From #islands V
WHERE D.Account BETWEEN V.AccountFrom AND V.AccountTo
) V
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'crossapply','',DATEDIFF(MS,#RunDate,GETDATE()))
SET #RunDate=GETDATE()
SELECT D.Account, I.Dim
FROM #data D
JOIN #islands I
ON D.Account BETWEEN I.AccountFrom AND I.AccountTo
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'join','',DATEDIFF(MS,#RunDate,GETDATE()))
SET #RunDate=GETDATE()
;WITH cte AS
(
SELECT Account, AccountFrom, AccountTo, Dim, 1 AS Counting
FROM #islands
CROSS APPLY (VALUES(AccountFrom),(AccountTo)) V (Account)
UNION ALL
SELECT Account + 1 ,AccountFrom, AccountTo, Dim, Counting + 1
FROM cte
WHERE (Account + 1) > AccountFrom AND (Account + 1) < AccountTo
)
SELECT Account, Dim, Counting FROM cte OPTION(MAXRECURSION 32767)
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'recursivecte','',DATEDIFF(MS,#RunDate,GETDATE()))
You can select from the #times table to see the run times :)
I think you want a join, but using an inequality rather than an equality:
select tt.id, tt.dim1, it.dim2
from TallyTable tt join
IslandsTable it
on tt.id between it."from" and it."to"
This works for the data that you provide in the question.
Here is another idea that might work. Here is the query:
select d.*,
(select top 1 AccountType from #data d2 where d2.Account > d.Account and d2.AccountType not in (2, 5)
) nextAccountType
from #data d
order by d.account;
I just ran this on 50,000 rows and this version took 17 seconds on my system. Changing the table to:
CREATE TABLE #data (
Account INTEGER IDENTITY(1,1) primary key,
AccountType INTEGER,
);
Has actually slowed it down to about 1:33 -- quite to my surprise. Perhaps one of these will help you.

How to get previous and next row's value effeciently in SQL server

Say I have these rows,
InstrumentID
547
698
708
InstrumentID is not autogenerated column.
Say if I pass the parameter in procedure as 698, I should get previous value as 547 and next value as 708. How do I do this efficiently in SQL?
I have this procedure but it is not efficient (and not correct).
Alter PROCEDURE GetNextAndPreviousInsturmentID
(
#InstrumentID varchar(14),
#PreviousInstrumentID varchar(14) OUT,
#NextInstrumentID varchar(14) OUT
)
AS
BEGIN
Declare #RowNum int = 0
Select #RowNum = ROW_NUMBER() Over (Order by Cast(#InstrumentID as decimal(18))) From Documents Where InstrumentID = #InstrumentID
;With normal As
(
Select ROW_NUMBER() Over (Order by Cast(#InstrumentID as decimal(18))) as RowNum, Cast(InstrumentID as decimal(18)) as InstrumentID
From Documents
)
Select #PreviousInstrumentID = InstrumentID From normal
Where RowNum = #RowNum - 1
Select #NextInstrumentID = InstrumentID From normal
Where RowNum = #RowNum + 1
END
GO
Here is a simpler solution, still it's more efficient
SELECT P.PreviousID, N.NextID
FROM
(SELECT MAX(D.InstrumentID) PreviousID
FROM Documents D
WHERE InstrumentID < #InstrumentID) P
CROSS JOIN
(SELECT MIN(D.InstrumentID) NextID
FROM Documents D
WHERE InstrumentID > #InstrumentID) N
Try this:
Alter PROCEDURE GetNextAndPreviousInsturmentID
(
#InstrumentID varchar(14),
#PreviousInstrumentID varchar(14) OUT,
#NextInstrumentID varchar(14) OUT
)
AS
BEGIN
Declare #Ids TABLE(Id varchar(14))
;With normal As
(
--Numerate our rows
Select ROW_NUMBER() Over (Order by Cast(Documents.InstrumentID as decimal(18)) as RowNumber,
Documents.InstrumentID
From Documents
)
--Insert three rows from our table with our id and previos/next id
INSERT INTO #Ids(Id)
SELECT TOP(3) normal.InstrumentID
FROM normal
WHERE RowNumber >=
(
SELECT RowNumber - 1
FROM normal
WHERE normal.InstrumentID = #InstrumentID
)
ORDER BY normal.RowNumber
--select next and previos ids
SELECT #PreviousInstrumentID = Min(CAST(Id as decimal(18))),
#NextInstrumentID = MAX(CAST(Id as decimal(18)))
FROM #Ids
END
GO
In MS SQL 2012 we have new window functions like FIRST_VALUE and LAST_VALUE, unfortunately in sql 2008 these functions are missing.
WITH CTE AS (
SELECT rownum = ROW_NUMBER() OVER (ORDER BY p.LogDate), p.LogDate
FROM DeviceLogs p
)
SELECT prev.logdate PreviousValue, CTE.logdate, nex.logdate NextValue
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
GO
select
LAG(InstrumentID) OVER (ORDER BY InstrumentID) PreviousValue,
InstrumentID,
LEAD(InstrumentID) OVER (ORDER BY InstrumentID) NextValue
from documents
Hi I think this will be much more efficient:
Select Next :
select top 1 ID from mytable
where ID >'698'
order by ID asc
Select Prev:
select top 1 ID from mytable
where ID <'698'
order by ID desc

Always return a specified number of records Sql Server

I have a query that I always want to return 10 records for:
set rowcount 10
select row_number() over(order by count(*) desc) row_num
,hist_report_id
,max(rpt_report_name) report_name
from utility.dbo.tbl_report_history
join utility.dbo.tbl_report_definitions
on hist_report_id = rpt_report_id
where hist_user_id = 1038
group by hist_report_id
Which works fine if I have 10 or more records. The problem is when there are less than 10 records, I still need to return the rownumber field with nulls in the report_id and report_name fields.
If there were only 7 records returned, the results should look like:
row_num report_id report_name
1 id1 name1
2 id2 name2
3 id3 name3
4 id4 name4
5 id5 name5
6 id6 name6
7 id7 name7
8 null null
9 null null
10 null null
Any suggestions?
I am using SQL Server 2008
count() can never return less than zero... so just append 10 dummy rows with -1 in the count column via union
Also, don't use SET ROWCOUNT because it affects intermediate results
SELECT TOP 10
row_number() over(order by TheCount desc) AS row_num,
hist_report_id,
report_name
FROM
(
select
,count(*) AS TheCount
,hist_report_id
,max(rpt_report_name) AS report_name
from
utility.dbo.tbl_report_history
join
utility.dbo.tbl_report_definitions on hist_report_id = rpt_report_id
where hist_user_id = 1038
group by hist_report_id
UNION ALL
SELECT TOP 10
-1, NULL, NULL
FROM sys.columns
) T
with
/* Create 10 dummy rows using recursion */
RowSequence as (
select 0 Idx
union all
select RowSequence.Idx+1 Idx from RowSequence where RowSequence.Idx+1<10
),
/* Your Query */
YourQuery as(
select row_number() over(order by count(*) desc) row_num
,hist_report_id
,max(rpt_report_name) report_name
from utility.dbo.tbl_report_history
join utility.dbo.tbl_report_definitions
on hist_report_id = rpt_report_id
where hist_user_id = 1038
group by hist_report_id
)
/* Now all together */
select top 10 * from (
select * from YourQuery
union all
select null, null, null from RowSequence
) a
/* To avoid the recursion maximum level 100 */
option (maxrecursion 0)
The simplest way that I recall:
Insert the select result in a #temptable
While count(*) < 10 insert row into #temptable
You could consider using a TVF to return for you a set of numbers that you could outer join with:
CREATE FUNCTION SetOfValues (#beginningAt int, #endingAt int, #step int = 1)
RETURNS
#result TABLE (value int )
AS BEGIN
declare #counter int set #counter = #beginningAt
while(#counter<=#endingAt)
begin
insert into #result values(#counter)
set #counter = #counter + #step
end
RETURN
END
GO
-- USAGE
with cte as (
select row_number() over (order by hist_report_Id) as row_num,
hist_report_Id, report_name
from tbl_report_history join tbl_report_definitions on hist_report_Id = rpt_reportId)
select value, hist_reportId, report_name
from dbo.SetOfValues(1,10,1) as t on t.value = cte.row_num

Group by numbers that are in sequence

I have some data like this:
row id
1 1
2 36
3 37
4 38
5 50
6 51
I would like to query it to look like this:
row id group
1 1 1
2 36 2
3 37 2
4 38 2
5 50 3
6 51 3
... so that I can GROUP BY where the numbers are consecutively sequential.
Also, looping/cursoring is out of the question since I'm working with a pretty large set of data, thanks.
;WITH firstrows AS
(
SELECT id, ROW_NUMBER() OVER (ORDER BY id) groupid
FROM Table1 a
WHERE id - 1 NOT IN (SELECT b.id FROM Table1 b)
)
SELECT id,
(
SELECT MAX(b.groupid)
FROM firstrows b
WHERE b.id <= a.id
) groupid
FROM Table1 a
with
data(row, id) as (
select *
from (
values
(1,1)
,(2,36)
,(3,37)
,(4,38)
,(5,50)
,(6,51)
) as foo(row, id)
),
anchor(row, id) as (
select row, id
from data d1
where not exists(select 0 from data d2 where d2.id = d1.id - 1)
)
select d1.*, dense_rank() over(order by foo.id) as thegroup
from
data d1
cross apply (select max(id) from anchor where anchor.id <= d1.id) as foo(id)
order by
d1.row
;
This solution does more work that is strictly necessary on the basis that there may be gaps in the sequence of row values, and on the assumption that those gaps should be ignored.
Set up test data:
DECLARE #table TABLE
(ROW INT,
id INT
)
INSERT #table
SELECT 1,1
UNION SELECT 2,36
UNION SELECT 3,37
UNION SELECT 4,38
UNION SELECT 5,50
UNION SELECT 6,51
Output query
;WITH grpCTE
AS
(
SELECT ROW, id,
ROW_NUMBER() OVER (ORDER BY ROW
) AS rn
FROM #table
)
,recCTE
AS
(
SELECT ROW, id, rn, 1 AS grp
FROM grpCTE
WHERE rn = 1
UNION ALL
SELECT g.row, g.id, g.rn, CASE WHEN g.id = r.id + 1 THEN r.grp ELSE r.grp + 1 END AS grp
FROM grpCTE AS g
JOIN recCTE AS r
ON g.rn = r.rn + 1
)
SELECT row, id, grp FROM recCTE
create table #temp
(
IDUnique int Identity(1,1),
ID int,
grp int
)
Insert into #temp(ID) Values(1)
Insert into #temp(ID) Values(36)
Insert into #temp(ID) Values(37)
Insert into #temp(ID) Values(38)
Insert into #temp(ID) Values(50)
Insert into #temp(ID) Values(51)
declare #IDUnique int
declare #PreviousUnique int
declare #ID int
declare #grp int
declare #Previous int
declare #Row int
DECLARE #getAccountID CURSOR SET #getAccountID = CURSOR FOR SELECT Row_Number() Over(Order by IDUnique) Row, IDUnique, ID From #temp
OPEN #getAccountID
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#Row = 1)
Begin
update #temp set grp = 1 Where IDUnique = #IDUnique
set #Previous = #ID
set #grp = 1
End
Else If (#Previous + 1 = #ID)
Begin
update #temp set grp = #grp Where IDUnique = #IDUnique
set #Previous = #ID
End
Else
Begin
set #Previous = #ID
set #grp = #grp + 1
update #temp set grp = #grp Where IDUnique = #IDUnique
End
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID
END
CLOSE #getAccountID
DEALLOCATE #getAccountID
Select * from #temp
Drop Table #temp
Select T.Id, T.Row, groupId as "Group", dr FROM tbrows T
Left Outer Join
(
Select min(id) as groupId,DENSE_RANK() over( order by min(id)) as dr, MIN(row-id) as d, Sum(1) as s FROM tbrows
Group BY (row-id)
) U
On (T.Id >= U.groupId) and (T.Id < U.groupId+U.s)
Order By T.Id

Add null's for numerics using CTE in Sql Server 2005

I have some data as below
ID Data
1 a
1 2
1 b
1 1
2 d
2 3
2 r
Desired Output
ID Data
1 a
1 NULL
1 NULL
1 b
1 NULL
2 d
2 NULL
2 NULL
2 NULL
2 r
What the output is , for the numerics it is replaced with those many null values. E.g. for a numeric value of 2 , it will be 2 null values.
The ddl is as under
Declare #t table(ID int, data varchar(10))
Insert into #t
Select 1, 'a' union all select 1, '2' union all select 1, 'b' union all select 1, '1' union all select 2,'d' union all
select 2,'3' union all select 2, 'r'
select * from #t
Please give a CTE based solution. I already have the procedural approach but I need to have a feel of CTE based solution.
Solution I am using at present
CREATE FUNCTION [dbo].[SPLIT] (
#str_in VARCHAR(8000)
)
RETURNS #strtable TABLE (id int identity(1,1), strval VARCHAR(8000))
AS
BEGIN
DECLARE #tmpStr VARCHAR(8000), #tmpChr VARCHAR(5), #ind INT = 1, #nullcnt INT = 0
SELECT #tmpStr = #str_in
WHILE LEN(#tmpStr) >= #ind
BEGIN
SET #tmpChr = SUBSTRING(#tmpStr,#ind,1)
IF ISNUMERIC(#tmpChr) = 0
INSERT INTO #strtable SELECT #tmpChr
ELSE
WHILE #nullcnt < #tmpChr
BEGIN
INSERT INTO #strtable SELECT NULL
SET #nullcnt = #nullcnt + 1
END
SELECT #ind = #ind + 1, #nullcnt = 0
END
RETURN
END
GO
Invocation
SELECT * from dbo.SPLIT('abc2e3g')
I donot want to do it using function but a CTE solution.
Reason: I am learning CTE and trying to solve problems using it. Trying to come out of procedural/rBar approach.
Thanks
Setup:
declare #t table(UniqueID int identity(1,1), ID int, data varchar(10))
insert into #t
select 1, 'a' union all
select 1, '2' union all
select 1, 'b' union all
select 1, '1' union all
select 2, 'd' union all
select 2, '3' union all
select 2, 'r'
Query:
;with cte
as
(
select UniqueId, id, data, case when isnumeric(data) = 1 then cast(data as int) end Level
from #t
union all
select UniqueId, id, null, Level - 1
from cte
where Level > 0
)
select id, data
from cte
where Level is null or data is null
order by UniqueID, Level
Output:
id data
----------- ----------
1 a
1 NULL
1 NULL
1 b
1 NULL
2 d
2 NULL
2 NULL
2 NULL
2 r
I added UniqueId as there should be some kind of field that specifies order in the original table.
If you have a numbers table, this becomes quite simple.
;WITH Nbrs ( Number ) AS (
SELECT 1 UNION ALL
SELECT 1 + Number FROM Nbrs WHERE Number < 8000 )
SELECT
M.UniqueID, M.ID, Data
FROM
#t M
WHERE
ISNUMERIC(M.Data) = 0
UNION ALL
SELECT
M.UniqueID, M.ID, NULL
FROM
#t M
JOIN --the <= JOIN gives us (M.Data) rows
Nbrs N ON N.Number <= CASE WHEN ISNUMERIC(M.Data) = 1 THEN M.Data ELSE NULL END
ORDER BY
UniqueID
OPTION (MAXRECURSION 0)
Edit: JOIN was wrong way around. Oops.
Numbers CTE taken from here