Group by numbers that are in sequence - sql

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

Related

How to effectively split grouped records into batches

For each group in table I need to split that group into specific amount of records (batches) and mark each record in batch with according batch id.
Right now, my implementation based on cursors is IMHO clumsy. It takes 1 minute to split set of 10 000 rows which is, needless to say, very slow. Any clues how to make that work faster?
Here is test script.
-- Needed to generate big data
DECLARE #Naturals TABLE (ID INT)
INSERT INTO #Naturals (ID)
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
DECLARE #TestData TABLE
(
LINK INT,
F_House INT,
F_Batch UNIQUEIDENTIFIER
)
INSERT INTO #TestData (LINK, F_House)
SELECT ROW_NUMBER() OVER (order by T1.ID), ROW_NUMBER() OVER (order by T1.ID) % 5
FROM
#Naturals T1
CROSS JOIN #Naturals T2
CROSS JOIN #Naturals T3
CROSS JOIN #Naturals T4
--CROSS JOIN #Naturals T5 -- that would give us 100 000
-- Finished preparing Data (10 000 rows)
SELECT 'Processing:', COUNT(*) FROM #TestData
DECLARE #batchSize INT -- That would be amount of rows in each batch
SET #batchSize = 50
IF OBJECT_ID('tempdb..#G') IS NOT NULL -- Split set of data into groups. We need to create batches in each group.
DROP TABLE #G
SELECT
buf.F_House, COUNT(*) AS GroupCount
INTO #G
FROM #TestData buf
GROUP BY buf.F_House -- That logic could be tricky one. Right now simplifying
DECLARE #F_House INT -- That would be group key
DECLARE db_cursor CURSOR FOR
SELECT F_House
FROM #G
ORDER BY F_House
OPEN db_cursor FETCH NEXT FROM db_cursor INTO #F_House
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Processing house group: ' + CAST(#F_House AS VARCHAR(10))
-- For each group let's create batches
WHILE EXISTS (SELECT 1 FROM #TestData AS itmds
WHERE itmds.F_House = #F_House
AND itmds.F_Batch IS NULL
)
BEGIN
DECLARE #batchLink UNIQUEIDENTIFIER
SET #batchLink = NEWID()
UPDATE itmds
SET itmds.F_Batch = #batchLink
FROM #TestData AS itmds
WHERE itmds.F_House = #F_House
AND itmds.F_Batch IS NULL
AND itmds.LINK IN
(
SELECT TOP (#batchSize)
sub.LINK
FROM #TestData sub
WHERE sub.F_House = #F_House
AND sub.F_Batch IS NULL
)
END
FETCH NEXT FROM db_cursor INTO #F_House
END
CLOSE db_cursor
DEALLOCATE db_cursor
SELECT
buf.F_House, COUNT(distinct F_Batch) AS BatchCountInHouse
FROM #TestData buf
GROUP BY buf.F_House
ORDER BY buf.F_House
Expected output (considering batchsize = 50)
10 000 rows / 5 houses = 2000 rows/house
2000 rows/house / 50(batchSize) = 40 batches/house
This is set based avoiding a cursor. The assigned F_Batch is a BIGINT:
;with baseRowNum as
(
SELECT LINK, F_House,
-- row number per F_House
Row_Number() Over (PARTITION BY F_House ORDER BY LINK) AS rn
FROM #TestData
)
SELECT *,
-- combine F_House & group number into a unique result
F_House * 10000 +
-- start a new sub group for every F_House or after #batchSize rows
Sum(CASE WHEN rn % #batchSize = 1 THEN 1 ELSE 0 END)
Over (ORDER BY F_House, rn
ROWS Unbounded Preceding) AS F_Batch
FROM baseRowNum
If you really need a UNIQUEINDENTIFIER you can join back:
;with baseRowNums as
(
SELECT LINK, F_House,
-- row number per F_House
Row_Number() Over (PARTITION BY F_House ORDER BY LINK) AS rn
FROM #TestData
)
,batchNums as
(
SELECT *,
-- combine F_House & group number into a unique result
F_House * 10000 +
-- start a new sub group for every F_House or after #batchSize rows
Sum(CASE WHEN rn % #batchSize = 1 THEN 1 ELSE 0 END)
Over (ORDER BY F_House, rn
ROWS Unbounded Preceding) AS F_Batch
FROM baseRowNums
)
,GUIDs as
(
select F_Batch, MAX(newid()) as GUID
from batchNums
group by F_Batch
)
-- select * from
--from batchNums join GUIDs
-- on batchNums.F_Batch = GUIDs.F_Batch
select F_House, GUID, count(*)
from batchNums join GUIDs
on batchNums.F_Batch = GUIDs.F_Batch
group by F_House, GUID
order by F_House, count(*) desc
See Fiddle.
I would use an inner looping inside of a looping referencing a grouping level.
Then you can iterate through from the grouping down into the BatchGrouping. However as you pointed out speed is an issue with table variables and CTE's for that reason I in this case tested with a tempdb # table. This way I could index after the insert and optimize performance. I can run a million rows of aggregation logic in about 16 seconds. I consider that acceptable performance. But my Dev Box is an I7 6700, with 16 gigs of DDR4, and an SSD. Performance times may vary based on hardware obviously.
--Make up some fake data for example
DECLARE
#Start INT = 1
, #End INT = 100000
;
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE tempdb..#Temp
CREATE Table #Temp (Id INT, Grp int, Val VARCHAR(8), BatchGroup int)
WHILE #Start <= #End
BEGIN
INSERT INTO #Temp (Id, Grp, Val)
VALUES (#Start, CAST(RAND() * 8 AS INT) + 1, LEFT(NEWID(), 8))
SELECT #Start += 1;
END
CREATE CLUSTERED INDEX IX_Temp_Grp ON #Temp(Grp, BatchGroup)
--Determine Batch Size You want for groupings
DECLARE #BatchSize INT = 1000;
--Let's randomly mess with groupings
DECLARE #X INT = 1
WHILE #X <= 4
BEGIN
; WITH x AS
(
SELECT TOP (#BatchSize * 4)
Id
, Grp
, Val
FROM #Temp
WHERE Grp = CAST(RAND() * 8 AS INT) + 1
)
UPDATE x
SET Grp = CAST(RAND() * 8 AS INT) + 1
SELECT #X += 1
END
DECLARE
#CurrentGroup INT = 1
, #CurrentBatch INT = 1
WHILE #CurrentGroup <= (SELECT MAX(Grp) FROM #Temp) -- Exists (SELECT 1 FROM #Temp WHERE BatchGroup IS NULL)
BEGIN
WHILE EXISTS (SELECT 1 FROM #Temp WHERE Grp = #CurrentGroup AND BatchGroup IS NULL)
BEGIN
; WITH x AS
(
SELECT TOP (#BatchSize) *
FROM #Temp
WHERE Grp = #CurrentGroup
AND BatchGroup IS NULL
)
update x
SET BatchGroup = #CurrentBatch
SELECT #CurrentBatch += 1;
END
SELECT #CurrentBatch = 1
SELECT #CurrentGroup += 1;
END
--Proof
Select
Grp
, COUNT(DISTINCT Id)
, COUNT(DISTINCT BatchGroup)
From #Temp
GROUP BY Grp
Actually, I've tried NTILE() with cursors and it's quite fast(I mean its faster then 1 minute for 10 000 rows).
10 000 rows for 0 seconds.
100 000 rows for 3 seconds.
1 000 000 rows for 34 seconds.
10 000 000 rows for 6 minutes
Linear grow in complexity which is nice.
-- Needed to generate big data
DECLARE #Naturals TABLE (ID INT)
INSERT INTO #Naturals (ID)
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
DECLARE #TestData TABLE
(
LINK INT,
F_House INT,
F_Batch UNIQUEIDENTIFIER
)
INSERT INTO #TestData (LINK, F_House)
SELECT ROW_NUMBER() OVER (order by T1.ID), ROW_NUMBER() OVER (order by T1.ID) % 5
FROM
#Naturals T1
CROSS JOIN #Naturals T2
CROSS JOIN #Naturals T3
CROSS JOIN #Naturals T4
--CROSS JOIN #Naturals T5 -- that would give us 100 000
-- Finished preparing Data (10 000 rows)
SELECT 'Processing:', COUNT(*) FROM #TestData
DECLARE #batchSize INT -- That would be amount of rows in each batch
SET #batchSize = 50
IF OBJECT_ID('tempdb..#G') IS NOT NULL -- Split set of data into groups. We need to create batches in each group.
DROP TABLE #G
SELECT
buf.F_House, COUNT(*) AS GroupCount
INTO #G
FROM #TestData buf
GROUP BY buf.F_House -- That logic could be tricky one. Right now simplifying
DECLARE #F_House INT -- That would be group key
DECLARE db_cursor CURSOR FOR
SELECT F_House
FROM #G
ORDER BY F_House
OPEN db_cursor FETCH NEXT FROM db_cursor INTO #F_House
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Processing house group: ' + CAST(#F_House AS VARCHAR(10))
DECLARE #rowsInGroup INT
SELECT #rowsInGroup = COUNT(*) FROM #TestData
WHERE F_House = #F_House
IF OBJECT_ID('tempdb..#TileBatch') IS NOT NULL
DROP TABLE #TileBatch
SELECT
T.[NTile], NEWID() AS F_Batch
INTO #TileBatch
FROM
(
SELECT distinct
NTILE(#rowsInGroup / #batchSize) OVER (ORDER BY LINK) AS [NTile]
from
#TestData
WHERE F_House = #F_House
) T
UPDATE D
SET D.F_Batch = B.F_Batch
FROM
#TestData D
INNER JOIN
(
SELECT
*, NTILE(#rowsInGroup / #batchSize) OVER (ORDER BY LINK) AS [NTile]
from
#TestData
WHERE F_House = #F_House
) DT ON D.LINK = DT.LINK
INNER JOIN
#TileBatch B ON DT.[NTile] = B.[NTile]
WHERE D.F_House = #F_House
FETCH NEXT FROM db_cursor INTO #F_House
END
CLOSE db_cursor
DEALLOCATE db_cursor
SELECT
buf.F_House, COUNT(distinct F_Batch) AS BatchCountInHouse
FROM #TestData buf
GROUP BY buf.F_House
ORDER BY buf.F_House

How to tune the following query?

This query gives me the desired result but i can't run this query every time.The 2 loops is costing me.So i need to implement something like view.But the logic has temp tables involved which isn't allowed in views as well.so, is there any other way to store this result or change the query so that it will cost me less.
DECLARE #Temp TABLE (
[SiteID] VARCHAR(100)
,[StructureID] INT
,[row] DECIMAL(4, 2)
,[col] DECIMAL(4, 2)
)
DECLARE #siteID VARCHAR(100)
,#structureID INT
,#struct_row INT
,#struct_col INT
,#rows_count INT
,#cols_count INT
,#row INT
,#col INT
DECLARE structure_cursor CURSOR
FOR
SELECT StructureID
,SiteID
,Cols / 8.5 AS Cols
,Rows / 11 AS Rows
FROM Structure
WHERE SellerID = 658 --AND StructureID = 55
OPEN structure_cursor
FETCH NEXT
FROM structure_cursor
INTO #structureID
,#siteID
,#struct_col
,#struct_row
SELECT #rows_count = 1
,#cols_count = 1
,#row = 1
,#col = 1
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #row <= #struct_row
BEGIN
WHILE #col <= #struct_col
BEGIN
--PRINT 'MEssage';
INSERT INTO #Temp (
SiteID
,StructureID
,row
,col
)
VALUES (
#siteID
,#structureID
,#rows_count
,#cols_count
)
SET #cols_count = #cols_count + 1;
SET #col = #col + 1;
END
SET #cols_count = 1;
SET #col = 1;
SET #rows_count = #rows_count + 1;
SET #row = #row + 1;
END
SET #row = 1;
SET #col = 1;
SET #rows_count = 1;
FETCH NEXT
FROM structure_cursor
INTO #structureID
,#siteID
,#struct_col
,#struct_row
END
CLOSE structure_cursor;
DEALLOCATE structure_cursor;
SELECT * FROM #Temp
Do this with a set-based operation. I think you just want insert . . . select:
INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT StructureID, SiteID, Cols / 8.5 AS Cols, Rows / 11 AS Rows
FROM Structure
WHERE SellerID = 658;
You should avoid cursors, unless you really need them for some reason (such as calling a stored procedure or using dynamic SQL on each row).
EDIT:
Reading the logic, it looks like you want to insert rows for based on the limits in each row. You still don't want to use a cursor. For that, you need a number generator and master..spt_values is a convenient one, if it has enough rows. So:
with n as (
select row_number() over (order by (select null)) as n
from master..spt_values
)
INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT StructureID, SiteID, ncol.n / 8.5 AS Cols, nrow.n / 11 AS Rows
FROM Structure s JOIN
n ncol
ON ncol.n <= s.struct_col CROSS JOIN
n nrow
ON nrow <= s.struct_row
WHERE SellerID = 658;
You can generate the number of rows and columns and then CROSS APPLY with those, like below. I've left out your SellerID condition.
;WITH Cols
AS
(
SELECT StructureID, SiteID, CAST(Cols / 8.5 AS INT) AS Col
FROM Structure
UNION ALL
SELECT s.StructureID, s.SiteID, Col - 1
FROM Structure s
INNER JOIN Cols c ON s.StructureID = c.StructureID AND s.SiteID = c.SiteID
WHERE Col > 1
)
, Rows
AS
(
SELECT StructureID, SiteID, CAST(Rows / 11 AS INT) AS Row
FROM Structure
UNION ALL
SELECT s.StructureID, s.SiteID, Row - 1
FROM Structure s
INNER JOIN Rows r ON s.StructureID = r.StructureID AND s.SiteID = r.SiteID
WHERE Row > 1
)
--INSERT INTO #Temp (SiteID, StructureID, row, col)
SELECT s.SiteID, s.StructureID, r.Row, c.Col
FROM Structure s
CROSS APPLY Cols c
CROSS APPLY Rows r
WHERE s.StructureID = c.StructureID AND s.SiteID = c.SiteID
AND s.StructureID = r.StructureID AND s.SiteID = r.SiteID
We can do this by using CROSS APPLY and CTE.
CREATE TABLE Structure(SiteID varchar(20), StructureID int,
Cols decimal(18,2), [Rows] decimal(18,2))
INSERT INTO Structure (SiteID, StructureID, Cols, [Rows])
VALUES
('MN353970', 51,17,22),
('MN272252', 52,17,11)
;WITH RowCTE([Rows]) AS
(
SELECT 1
UNION ALL
SELECT 2
),
ColCTE(Cols) AS
(
SELECT 1
UNION ALL
SELECT 2
)
SELECT SiteID, StructureID, R.Rows, C.Cols
FROM Structure s
CROSS APPLY
(
SELECT Cols FROM ColCTE
) C
CROSS APPLY
(
SELECT [Rows] FROM RowCTE
) R
Sql Fiddle Demo

Row By Row ( Without Cursor or Loops)

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

Select TOP 2 results in variables without loop

I want to store the top 2 results in 2 variables.
create table t(id int);
insert into t (id) values (1),(2),(3),(4);
declare #id1 int
declare #id2 int
select top 2 #id1 = first id,
#id2 = next id
from t
SQLFiddle
Can I do it in one query without using a loop?
declare #id1 int,#id2 int
;with cte as (
select top (2) id
from t
order by id
)
select #id1 = min(id), #id2 = max(id)
from cte
select #id1,#id2
Fiddle demo
with cte as (
select top 2 id, row_number() over(order by id) as rn
from t
order by id
)
select
#id1 = (select id from cte where rn = 1),
#id2 = (select id from cte where rn = 2)
or
with cte as (
select top 2 id, row_number() over(order by id) as rn
from t
order by id
)
select
#id1 = max(case when rn = 1 then id end),
#id2 = max(case when rn = 2 then id end)
from cte
sql fiddle demo
You can use LEAD() for SQL Server 2012.
SELECT TOP 1 #id1 = ID, #id2 = LEAD(ID) OVER (ORDER BY ID) FROM t
SQLFiddle Demo
With two SELECT it's easy...
DECLARE #id1 INT
DECLARE #id2 INT
SELECT TOP 1 #id1 = x.id
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) RN FROM t) x
WHERE x.RN = 1
SELECT TOP 1 #id2 = x.id
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) RN FROM t) x
WHERE x.RN = 2
SELECT #id1, #id2
With SQL 2012 you clearly could
SELECT #id1 = id
FROM t ORDER BY id OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
SELECT #id2 = id
FROM t ORDER BY id OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY
Or evein in 2008 you could
; WITH Base AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY id) RN FROM t
)
SELECT #id1 = b1.id, #id2 = b2.id
FROM Base b1, Base b2
WHERE b1.RN = 1 AND B2.RN = 2
declare #id1 int
declare #id2 int
declare #table table(id int,rownum int)
insert into #table
select top 2 id,row_number() over( order by id) as rn from t
select #id1=case rownum when 1 then id else #id1 end,
#id2=case rownum when 2 then id end from #table
select #id1,#id2
SQL FIDDLE
More easy way with 2 selects:
declare #id1 int
declare #id2 int
select top 1 #id1 = id from t
select top 2 #id2 = id from t
select #id1, #id2
SQL Fiddle

Problem in string concatenation in sql server using FOR XML Path.

I have the below data
UniqueID ID data
1 1 a
2 1 2
3 1 b
4 1 1
5 2 d
6 2 3
7 2 r
The expected output being
ID ConcatData
1 a,-,-,b,-
2 d,-,-,-,r
What we have to do is that, the number of numeric charecters has to be replaced with those many dashes('-') and then we need to merge the data for the respective id's.
I am using the below query so far
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'
select * from #t
;with cte1 as
(
select
UniqueId
, id
, data
, case when isnumeric(data) = 1 then cast(data as int) end Level
from #t
union all
select
UniqueId
, id
, CAST('-' as varchar(10))
, Level - 1
from cte1
where Level > 0 )
,cte2 as
(
select id, GroupID = Dense_Rank() Over(Order by id),data, DataOrder = ROW_NUMBER() over(order by UniqueID, Level)
from cte1
where Level is null or data = '-'
)
SELECT
ID
, (select data + ','
from cte2 t2
where t2.GroupID = t1.GroupID
for XML path('')
) as ConcatData
from cte2 t1
group by t1.ID ,t1.GroupID
But the output is
ID ConcatData
1 a,b,-,-,-,
2 d,r,-,-,-,
That is I am not able to position the dashes('-') in between the characters.
Please help
Try this:
;with cte1 as
(
select
UniqueId
, id
, data
,case when isnumeric(data) = 1
THEN replicate(',-',data)
ELSE ',' + data end as string
from #t
)
select
id
,LTRIM(STUFF(
(
SELECT
' ' + t2.String
FROM Cte1 t2
WHERE t2.id = t1.id
FOR XML PATH('')
), 2, 1, ''
)) As concatenated_string
from cte1 t1 group by t1.ID ,t1.ID
Works for the sample data bove and might be a bit quicker than using cursors
Following is the table creating
Create table #temp
(
IDUnique int Identity(1,1),
ID int,
data varchar(100)
)
Following are the records suggested by you.
Insert into #temp(ID, data) Values(1, 'a')
Insert into #temp(ID, data) Values(1, '2')
Insert into #temp(ID, data) Values(1, 'b')
Insert into #temp(ID, data) Values(1, '1')
Insert into #temp(ID, data) Values(2, 'd')
Insert into #temp(ID, data) Values(2, '3')
Insert into #temp(ID, data) Values(2, 'r')
Following is the cursor implementation
declare #IDUnique int
declare #ID int
declare #data varchar(100)
declare #Latest int
declare #Previous int
declare #Row int
set #Latest = 1
set #Previous = 1
Create Table #temp1
(
ID int,
data varchar(100)
)
--SELECT Row_Number() Over(Order by IDUnique) Row, IDUnique, ID, data From #temp
DECLARE #getAccountID CURSOR SET #getAccountID = CURSOR FOR SELECT Row_Number() Over(Order by IDUnique) Row, IDUnique, ID, data From #temp
OPEN #getAccountID
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID, #data
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#Row = 1)
Begin
Set #Previous = #ID
Set #Latest = #ID
Insert into #temp1(ID, data)values(#Previous, #data)
End
Else If (#Previous <> #ID)
Begin
Set #Previous = #ID
Set #Latest = #ID
Insert into #temp1(ID, data)values(#Previous, #data)
End
Else
Begin
Declare #number int
if(ISNUMERIC(#data) = 1)
Begin
Set #number = Convert(int , #data)
While(#number <> 0)
Begin
Update #temp1 Set Data = Data + ',-' Where ID = #ID
Set #number = #number - 1
End
End
Else
begin
Update #temp1 Set Data = Data + ',' + #data Where ID = #ID
End
End
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID, #data
END
CLOSE #getAccountID
DEALLOCATE #getAccountID
Select * from #temp1
Select * from #temp
Drop Table #temp
here is the Final Result set