Get the next record using while loop - sql

I'm not allowed to use cursor, or temp tables. I have to use a while loop only.
Table1 - Readonly Table (non editable)
id name
M01 Raja
M02 Ravi
M03 Vijay
M04 suresh
Query
Declare #TotRecord int, #CurrRecord Int, #id varchar(10)
Select #TotRec = COUNT(*) from Table1
Set #CurrRec = 1
WHILE (#CurrRec <=#TotRec)
BEGIN
--*Here i want to get the id from table,
--next time i need to get second id.
--next time i need to get third id.
--....
--1st time i can get the top 1 id by using this below query
Select top 1 #id = id from table
--Next time i want to get second id.*
SET #CurrRec = #CurrRec + 1
END

Use the pattern of
SELECT TOP 1 * FROM table WHERE id > #iterator ORDER BY id ASC
in a loop

DECLARE #TotRecord INT;
DECLARE #CurrRecord INT;
DECLARE #id VARCHAR(10);
SELECT #TotRecord = COUNT(*) from Table1;
SELECT #CurrRecord = 1;
SELECT TOP 1 #id = id FROM Table1 ORDER BY id;
WHILE (#CurrRecord <= #TotRecord)
BEGIN
--Do something...
--Get the next id
SELECT TOP 1 #id = id FROM Table1 WHERE id > #id ORDER BY id;
SELECT #CurrRecord = #CurrRecord + 1;
END;
But note that this will only work if the id is unique in Table1.

Declare #TotRecord int, #CurrRecord Int, #id varchar(10)
Select #TotRec = COUNT(*) from Table1
Set #CurrRec = 1
WHILE (#CurrRec <=#TotRec)
BEGIN
select LAST(id) from (SELECT TOP #CurrRec id
FROM Table1);
//Here you will get the last record, i havent tried this, but iam sure about the logic , hope this one helps
SET #CurrRec = #CurrRec + 1
END

If ID is sequence number (M01, M02, M03,...), then you can use the following with in while loop
SELECT TOP 1 #id = id FROM table WHERE CAST(REPLACE(id,'M','') AS INT) = #CurrRecord
Full Query
DECLARE #TotRecord INT, #CurrRecord INT, #id VARCHAR(10)
SELECT #TotRecord = COUNT(*) FROM Table1
SET #CurrRecord = 1
WHILE #CurrRecord <= #TotRecord
BEGIN
-- Get ID
SELECT TOP 1 #id = id FROM Table1 WHERE CAST(REPLACE(id,'M','') AS INT) = #CurrRecord
-- Do something
SET #CurrRecord = #CurrRecord + 1
END

You can use cursor for get records of your query one to one.
For example :
Declare #Col1 INT,
#Col2 INT,
...
DECLARE X CURSOR FOR
SELECT Col1, col2 , ...
FROM YourTableName
OPEN X
FETCH NEXT FROM X INTO #Col1, #Col2, ...
WHILE ##FETCH_STATUS=0 BEGIN
--your code to do with columns data of your record
FETCH NEXT FROM X INTO #Col1, #Col2, ...
END
CLOSE X
DEALLOCATE X
You can also use where for your idea:
DECLARE #Id INT,
#OldId INT,
#Col1 INT,
#Col2 INT
...
WHILE 1=1 BEGIN
SET #OldId = #Id
SET #Id = NULL
SELECT TOP 1 #Id = Id,
#Col1 = Column1,
#Col2 = Column2
FROM YourTable
WHERE (#OldId IS NULL OR Id > #OldId) ORDER BY Id
IF #Id IS NULL BREAK
-- your Code Here
END

Select id,name,row_number() over (order by id) 'Row' into #temp
from table
Select #id = id from #temp where row = #current
Use temp table to get the next id

Related

Updating two tables at once with values off the first one, and a variable

I'm trying to update with a passed variable in only the first row that has value NULL (multiple rows could have NULL in this column, but I need just the one),
Then I need to get the row affected (the primary key) and update the other table with it.
Here's what my two tables look like:
table1
id | some_value | ref_table2_id_fk
table2
id | name | ref_table1_id_fk
In my stored procedure I'm grabbing the passed value as #passed as int, then I try the following:
BEGIN
SET NOCOUNT ON;
DECLARE #id AS INT;
DECLARE #temp TABLE (id int);
BEGIN TRANSACTION;
BEGIN TRY
UPDATE TOP (1) [dbo].table1
SET ref_table2_id_fk = #passed
OUTPUT inserted.id INTO #temp
WHERE ref_table2_id_fk = NULL
UPDATE [dbo].table2
SET ref_table1_id_fk = #temp.id
FROM table2
JOIN #temp i on i.id = table2.id;
SET #id = ##IDENTITY
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
--some error
ROLLBACK TRANSACTION;
RETURN 0;
END
END CATCH;
IF ##TRANCOUNT > 0
BEGIN
--success
COMMIT TRANSACTION;
RETURN #Id;
END
END
As pointed out by Dale ##identity doesn't work in update. My intention is to simply know if the transaction went through or not.
I think the following code does what you are asking. Things fixed:
where ref_table2_id_fk = null should be where ref_table2_id_fk is null
You can't use TOP in an update statement you need a sub-query to get the id.
You're not providing an id in #temp to join onto table2 - you need the table1 id and the table2 id for a joined update.
If I understand your logic the id you want to return is #passed - you already have it.
#temp.id should be i.id since you've (rightly) aliased it
declare #Passed int = 3;
declare #table1 table (id int, some_value varchar(12), ref_table2_id_fk int);
declare #table2 table (id int, some_value varchar(12), ref_table1_id_fk int);
insert into #table1 (id)
select 1 union all select 2;
insert into #table2 (id)
select 3 union all select 4;
select * from #table1;
select * from #table2;
DECLARE #id AS INT, #Result bit = 0;
DECLARE #temp TABLE (id int, fk int);
BEGIN TRANSACTION;
BEGIN TRY
UPDATE #table1
SET ref_table2_id_fk = #passed
OUTPUT #passed, inserted.id INTO #temp
WHERE id = (
select top 1 id
from #table1
where ref_table2_id_fk is NULL
-- Optionally order by if you have a priority here
);
UPDATE T2
SET ref_table1_id_fk = i.fk
FROM #table2 T2
JOIN #temp i on i.id = T2.id
where T2.id = #passed;
-- If we get here then everything worked
-- Return #Result at the end of the proc
SET #Result = 1;
END TRY
begin catch
no_op:;
end catch
select * from #table1;
select * from #table2;

Comma Separated List from SQL Grouping

I'm trying to build comma separated list per group in sql,
As am using Parallel Data Warehouse i need to do this without using FOR XML or recursive function ( not supported ).
any other way to achieve this ?
Input:
ID Value
1 2
1 3
1 4
2 1
2 10
Output:
ID List
1 2,3,4
2 1,10
This will not perform well at all so I recommend you use some other solution (like a SQL Server linked server to PDW) if you need performance. But I believe this should work on PDW:
declare #ID int = (select min(ID) from tbl);
declare #Value int = -1;
declare #concat varchar(8000) = '';
create table #tmp (
ID int,
[concat] varchar(8000)
)
with (distribution=hash(ID), location=user_db);
while #ID is not null
begin
set #Value = (select min([Value]) from tbl where ID = #ID and [Value]>#Value);
if #Value is not null
set #concat = #concat + case when #concat = '' then '' else ',' end + cast(#Value as varchar(8000));
else
begin
insert #tmp (ID, [concat])
values (#ID, #concat);
set #ID = (select min(ID) from tbl where ID > #ID);
set #Value = -1;
set #concat = '';
end
end
select * from #tmp;
drop table #tmp;

Conditional Recursive SQL Select

Consider the following database table. It consists of 3 columns: Id, ParentId, Enabled.
I would like to produce a result set similar to the following. Basically for each record that has a Parent ID, I want to to display an additional column Enabled Parent Id. This column basically needs to recursively checks the hierarchy of the key, and stops when a key that is Enabled = True is found.
I would like this to achieve this on the fly, without requiring to add any additional computed columns in the table.
Maybe this could be achieved using a CTE.
Try this CTE query:
WITH T1 as
(SELECT id,
parentId,
NULL as EnabledParentId,
ParentID as NextParent
FROM T
WHERE ParentID is not null
UNION ALL
SELECT T1.id,
T1.parentId,
CASE WHEN T.enabled = 1
THEN T.ID
ELSE NULL END
as EnabledParentId,
T.ParentID as NextParent
FROM T1
JOIN T ON T1.NextParent = T.Id
WHERE (nextParent is not Null) and (EnabledParentId IS NULL)
)
SELECT ID,
ParentID,
EnabledParentID
FROM T1
WHERE EnabledParentId IS NOT NULL
OR NextParent IS NULL
ORDER BY ID;
DECLARE #myTable TABLE
(
Id INT NOT NULL,
ParentId INT NOT NULL,
EnabledParentId INT
)
DECLARE myCursor CURSOR FOR
SELECT Id
FROM T
WHERE ParentId IS NOT NULL
ORDER BY Id;
OPEN myCursor;
DECLARE #currentId INT;
FETCH NEXT FROM myCursor INTO #currentId;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Exists BIT = 0;
DECLARE #ParentId INT
SELECT #ParentId = ParentId
FROM T
WHERE Id = #currentId
WHILE (#ParentId IS NOT NULL AND #Exists = 0)
BEGIN
IF EXISTS(SELECT * FROM T WHERE Id = #ParentId AND IsEnabled = 1)
BEGIN
SET #Exists = 1
END
ELSE
BEGIN
SELECT #ParentId = ParentId
FROM T
WHERE Id = #ParentId
END
IF (#Exists = 1 OR #ParentId IS NULL)
BEGIN
INSERT INTO #myTable
SELECT Id, ParentId, #ParentId
FROM T
WHERE Id = #currentId
END
END
FETCH NEXT FROM myCursor INTO #currentId;
END
CLOSE myCursor;
DEALLOCATE myCursor;
SELECT *
FROM #myTable
ORDER BY 1

How do I loop through a set of records in SQL Server?

How do I loop through a set of records from a select statement?
Say I have a few records that I wish to loop through and do something with each record. Here's a primitive version of my select statement:
select top 1000 * from dbo.table
where StatusID = 7
By using T-SQL and cursors like this :
DECLARE #MyCursor CURSOR;
DECLARE #MyField YourFieldDataType;
BEGIN
SET #MyCursor = CURSOR FOR
select top 1000 YourField from dbo.table
where StatusID = 7
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
/*
YOUR ALGORITHM GOES HERE
*/
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;
This is what I've been doing if you need to do something iterative... but it would be wise to look for set operations first. Also, do not do this because you don't want to learn cursors.
select top 1000 TableID
into #ControlTable
from dbo.table
where StatusID = 7
declare #TableID int
while exists (select * from #ControlTable)
begin
select top 1 #TableID = TableID
from #ControlTable
order by TableID asc
-- Do something with your TableID
delete #ControlTable
where TableID = #TableID
end
drop table #ControlTable
Small change to sam yi's answer (for better readability):
select top 1000 TableID
into #ControlTable
from dbo.table
where StatusID = 7
declare #TableID int
while exists (select * from #ControlTable)
begin
select #TableID = (select top 1 TableID
from #ControlTable
order by TableID asc)
-- Do something with your TableID
delete #ControlTable
where TableID = #TableID
end
drop table #ControlTable
By using cursor you can easily iterate through records individually and print records separately or as a single message including all the records.
DECLARE #CustomerID as INT;
declare #msg varchar(max)
DECLARE #BusinessCursor as CURSOR;
SET #BusinessCursor = CURSOR FOR
SELECT CustomerID FROM Customer WHERE CustomerID IN ('3908745','3911122','3911128','3911421')
OPEN #BusinessCursor;
FETCH NEXT FROM #BusinessCursor INTO #CustomerID;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #msg = '{
"CustomerID": "'+CONVERT(varchar(10), #CustomerID)+'",
"Customer": {
"LastName": "LastName-'+CONVERT(varchar(10), #CustomerID) +'",
"FirstName": "FirstName-'+CONVERT(varchar(10), #CustomerID)+'",
}
}|'
print #msg
FETCH NEXT FROM #BusinessCursor INTO #CustomerID;
END
Just another approach if you are fine using temp tables.I have personally tested this and it will not cause any exception (even if temp table does not have any data.)
CREATE TABLE #TempTable
(
ROWID int identity(1,1) primary key,
HIERARCHY_ID_TO_UPDATE int,
)
--create some testing data
--INSERT INTO #TempTable VALUES(1)
--INSERT INTO #TempTable VALUES(2)
--INSERT INTO #TempTable VALUES(4)
--INSERT INTO #TempTable VALUES(6)
--INSERT INTO #TempTable VALUES(8)
DECLARE #MAXID INT, #Counter INT
SET #COUNTER = 1
SELECT #MAXID = COUNT(*) FROM #TempTable
WHILE (#COUNTER <= #MAXID)
BEGIN
--DO THE PROCESSING HERE
SELECT #HIERARCHY_ID_TO_UPDATE = PT.HIERARCHY_ID_TO_UPDATE
FROM #TempTable AS PT
WHERE ROWID = #COUNTER
SET #COUNTER = #COUNTER + 1
END
IF (OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
BEGIN
DROP TABLE #TempTable
END
You could choose to rank your data and add a ROW_NUMBER and count down to zero while iterate your dataset.
-- Get your dataset and rank your dataset by adding a new row_number
SELECT TOP 1000 A.*, ROW_NUMBER() OVER(ORDER BY A.ID DESC) AS ROW
INTO #TEMPTABLE
FROM DBO.TABLE AS A
WHERE STATUSID = 7;
--Find the highest number to start with
DECLARE #COUNTER INT = (SELECT MAX(ROW) FROM #TEMPTABLE);
DECLARE #ROW INT;
-- Loop true your data until you hit 0
WHILE (#COUNTER != 0)
BEGIN
SELECT #ROW = ROW
FROM #TEMPTABLE
WHERE ROW = #COUNTER
ORDER BY ROW DESC
--DO SOMTHING COOL
-- SET your counter to -1
SET #COUNTER = #ROW -1
END
DROP TABLE #TEMPTABLE
this way we can iterate into table data.
DECLARE #_MinJobID INT
DECLARE #_MaxJobID INT
CREATE TABLE #Temp (JobID INT)
INSERT INTO #Temp SELECT * FROM DBO.STRINGTOTABLE(#JobID,',')
SELECT #_MinJID = MIN(JobID),#_MaxJID = MAX(JobID) FROM #Temp
WHILE #_MinJID <= #_MaxJID
BEGIN
INSERT INTO Mytable
(
JobID,
)
VALUES
(
#_MinJobID,
)
SET #_MinJID = #_MinJID + 1;
END
DROP TABLE #Temp
STRINGTOTABLE is user define function which will parse comma separated data and return table. thanks
I think this is the easy way example to iterate item.
declare #cateid int
select CateID into [#TempTable] from Category where GroupID = 'STOCKLIST'
while (select count(*) from #TempTable) > 0
begin
select top 1 #cateid = CateID from #TempTable
print(#cateid)
--DO SOMETHING HERE
delete #TempTable where CateID = #cateid
end
drop table #TempTable

Update table via xml input

I have a stored proc that inserts new records in my 2008 SQL server table via xml input:
CREATE PROCEDURE ins_AddBinsToBox
#BoxId BIGINT,
#BinIds XML
AS
BEGIN
INSERT INTO WebServiceBoxDetails
(
BinId,
BoxId
)
SELECT
ParamValues.ID.value('.','VARCHAR(20)'),
#BoxId
FROM
#binIds.nodes('/Bins/id') AS ParamValues(ID)
This works great for inserting new rows, the thing i'm confused about is updating (via UPDATE statement) this same table with new xml input?
Table:
Id(PK) BoxNumber BinId
(bigint) (bigint) (int)
_______________________
1 12 334
2 12 445
3 12 776
4 16 223
5 16 669
Command to be used:
EXEC upd_Box #binIds='<Bins><id>7848</id><id>76554</id><id>71875</id></Bins>', #BoxId=12
Sorry for the delay. Here is the answer for your second part(this should work for both update and insert)
DECLARE #binIds AS XML = '<Bins><id>44</id><id>55</id><id>66</id><id>77</id></Bins>'
DECLARE #id INT
DECLARE #newid INT
DECLARE #count INT = 1
DECLARE #BinIdTable TABLE(RowNumber INT, BinId INT)
DECLARE #BoxNumber INT = 12
DECLARE #RowCount INT = 0
INSERT #BinIdTable(RowNumber, BinId)
SELECT ROW_NUMBER() OVER(ORDER BY ID), ParamValues.ID.value('.','INT')
FROM
#binIds.nodes('/Bins/id') AS ParamValues(ID)
SELECT #RowCount = COUNT(*) FROM #BinIdTable
DECLARE MyCursor CURSOR FOR
SELECT id FROM WebServiceBoxDetails WHERE BoxNumber = #BoxNumber ORDER BY id
OPEN MyCursor
FETCH NEXT FROM MyCursor
INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #newid = B.BinId
FROM #BinIdTable B
WHERE RowNumber = #count
UPDATE WebServiceBoxDetails
SET BinId = #newid
WHERE Id = #id
SET #count = #count + 1
FETCH NEXT FROM MyCursor
INTO #id
END
CLOSE MyCursor
DEALLOCATE MyCursor
IF #RowCount >= #count
BEGIN
INSERT INTO WebServiceBoxDetails
SELECT #BoxNumber, BinId
FROM #BinIdTable
WHERE RowNumber >= #count
END
Let me know how it goes.
You can use the NODES method again.
UPDATE WebServiceBoxDetails
SET BinID = ParamValues.ID.value('#BinID','VARCHAR(20)')
FROM
#bindIDs.nodes('/Bins/id') AS ParamValues(ID)
JOIN WebServiceBoxDetails w ON w.ID = ParamValues.ID.value('#id','VARCHAR(20)')
WHERE w.BoxNumber = #BoxID
Try this
DECLARE #binIds AS XML = '<Bins><id>7848</id><id>76554</id><id>71875</id></Bins>'
DECLARE #id INT
DECLARE #newid INT
DECLARE #count INT = 1
DECLARE #BinIdTable TABLE(RowNumber INT, BinId INT)
INSERT #BinIdTable(RowNumber, BinId)
SELECT ROW_NUMBER() OVER(ORDER BY ID), ParamValues.ID.value('.','INT')
FROM
#binIds.nodes('/Bins/id') AS ParamValues(ID)
DECLARE MyCursor CURSOR FOR
SELECT id FROM WebServiceBoxDetails WHERE BoxNumber = 12 ORDER BY id
OPEN MyCursor
FETCH NEXT FROM MyCursor
INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #newid = B.BinId
FROM #BinIdTable B
WHERE RowNumber = #count
UPDATE WebServiceBoxDetails
SET BinId = #newid
WHERE Id = #id
SET #count = #count + 1
FETCH NEXT FROM MyCursor
INTO #id
END
CLOSE MyCursor
DEALLOCATE MyCursor
Let me know how it goes.