Conditional Recursive SQL Select - sql

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

Related

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;

Calling a stored procedure for each row in a query

I am trying to call a stored proc in a loop for each record in a query.
Here's what I have now:
WITH X AS (
SELECT customerid FROM DBcustomers
)
WHILE(EXISTS (SELECT * FROM X))
BEGIN
SELECT TOP 1 #Id = customerid FROM X
EXEC [dbo].[AnotherSP] #Id , 1
DELETE FROM X WHERE customerid = #Id
END
, but this does not compile.
I guess you want to do UPDATE like this:
UPDATE ProductTable SET IsActive = 1
WHERE customerid IN (SELECT customerid FROM DBcustomers)
or for your edited question use cursors:
DECLARE #id BIG
DECLARE Customers CURSOR
FOR SELECT customerid FROM DBcustomers
OPEN Customers FETCH NEXT FROM Customers INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC [dbo].[AnotherSP] #id , 1
FETCH NEXT FROM Customers INTO #id
END
CLOSE Customers
DEALLOCATE Customers
UPDATE productTable
SET isActive = 1
WHERE customerId IN
(
SELECT customerId
FROM dbCustomers
)
Update:
DECLARE cr_customer CURSOR
FOR
SELECT customerId
FROM dbCustomers
DECLARE #id INT
FETCH NEXT
FROM cr_customer
INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC anotherSP #id , 1
FETCH NEXT
FROM cr_customer
INTO #id
END
CLOSE cr_customer
DEALLOCATE
cr_customer
Something like this might do it:
DECLARE #Id INT;
SELECT TOP 1 #Id = customerid FROM DBcustomers ORDER BY customerid;
WHILE #Id IS NOT NULL
BEGIN
EXEC [dbo].[AnotherSP] #Id, 1;
DECLARE #LastId INT;
SELECT #LastId = #Id;
SELECT #Id = NULL;
SELECT #Id = customerid FROM DBcustomers WHERE customerId > #LastId ORDER BY customerid;
END;

Get the next record using while loop

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

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

Issues with Trigger on insert (t-sql)

Say I have a self relation table as following :
ID - Name - ParentID
Now everytime that users insert sth in this table I would like to check if the Name inserted is already in the
rows where ParentID equals to the inserted one , if true then rollback the transaction.
But the problem is when I check the rows with the parentID from the inserted table the inserted row is already in the main table too. So, the trigger always rolls back the transaction.
Here is my trigger :
ALTER TRIGGER TG_Check_Existance_In_myTbl
ON myTbl FOR INSERT,UPDATE AS
DEClARE #result BIT
DECLARE #numberOfRows INT
DECLARE #counter INT
DECLARE #names nVARCHAR (30)
DECLARE #name NVARCHAR (30)
SET #result = 0
SET #numberOfRows = (SELECT COUNT (Name)
FROM myTbl
WHERE ParentID IN
(
SELECT ParentID
FROM inserted
)
)
SET #counter = 1;
SELECT #name = Name
FROM inserted
WHILE (#counter <= #numberOfRows)
BEGIN
WITH Q
AS
(
SELECT ROW_NUMBER()
OVER (ORDER BY Name) 'Row', Name
FROM myTbl WHERE ParentID IN
(
SELECT ParentID
FROM inserted
)
)
SELECT #names = Name
FROM Q
WHERE Row = #counter
IF #name = #names
SET #result=1;
SET #counter = #counter + 1
END
IF #result = 1
ROLLBACK TRAN
Unless I am missing something you are making this way too hard.
Why don't you use a unique constraint on the two columns?
table_constraint (Transact-SQL)