SQL Server generate script for views and how to decide order? - sql

I am generating the script for views using SQL Server built-in feature (Task -> Generate script). I am creating separate file for each object (of view). I have say around 400 files (containing SQL script of all views) to be executed on another database and to do that automatically I have created BAT file which takes care of that.
There are views which are dependent on other views and due to that many views failed to execute. Is there any way by which we can set order of execution and get rid off the failure ?
Any pointers would be a great help.
Please let me know if you need more details.
Thanks
Jony

Could you try this query? You can execute the create scripts in order to "gen" (generation).
DECLARE #cnt int = 0, #index int;
DECLARE #viewNames table (number int, name varchar(max))
DECLARE #viewGen table (id uniqueidentifier, gen int, name varchar(max), parentId uniqueidentifier)
INSERT INTO #viewNames
SELECT ROW_NUMBER() OVER(ORDER BY object_Id), name FROM sys.views
SELECT #cnt = COUNT(*) FROM #viewNames
SET #index = #cnt;
WHILE ((SELECT COUNT(*) FROM #viewGen) < #cnt)
BEGIN
DECLARE #viewName varchar(200)
SELECT #viewName = name FROM #viewNames WHERE number = #index;
DECLARE #depCnt int = 0;
SELECT #depCnt = COUNT(*) FROM sys.dm_sql_referencing_entities ('dbo.' + #viewName, 'OBJECT')
IF (#depCnt = 0)
BEGIN
INSERT INTO #viewGen SELECT NEWID(), 0, name, null FROM #viewNames WHERE number = #index;
END
ELSE
BEGIN
IF EXISTS(SELECT * FROM sys.dm_sql_referencing_entities ('dbo.' + #viewName, 'OBJECT') AS r INNER JOIN #viewGen AS v ON r.referencing_entity_name = v.name)
BEGIN
DECLARE #parentId uniqueidentifier = NEWID();
INSERT INTO #viewGen SELECT #parentId, 0, name, null FROM #viewNames WHERE number = #index;
UPDATE v
SET v.gen = (v.gen + 1), parentId = #parentId
FROM #viewGen AS v
INNER JOIN sys.dm_sql_referencing_entities('dbo.' + #viewName, 'OBJECT') AS r ON r.referencing_entity_name = v.name
UPDATE #viewGen
SET gen = gen + 1
WHERE Id = parentId OR parentId IN (SELECT Id FROM #viewGen WHERE parentId = parentId)
END
END
SET #index = #index - 1
IF (#index < 0) BEGIN SET #index = #cnt; END
END
SELECT gen as [order], name FROM #viewGen ORDER BY gen
Expecting result:
order name
0 vw_Ancient
1 vw_Child1
1 vw_Child2
2 vw_GrandChild

Related

SQL Query - Merge two types of objects with the same value

I want to find PC model pairs with the same speed and memory. These pairs are listed only once.
Here is my data and desired results:
desired results: https://i.imgur.com/cJBdrvq.png
data: https://i.imgur.com/t8LiJ7G.png
I did get results but the query is too long, I know there is a shorter way. Hope everyone help me.
Here is my query
DECLARE #FOR INT = 1
DECLARE #SPEED INT
DECLARE #RAM INT
DECLARE #MODEL INT
DECLARE #LIST TABLE(SPEED INT, RAM INT)
DECLARE #LISTMODEL TABLE(MODEL INT)
DECLARE #RESULT TABLE(PC1 INT, PC2 INT)
DECLARE #RESULTREAL TABLE(COUPLE NVARCHAR(20), SPEED INT, RAM INT)
DECLARE #COUNT INT
WHILE(1=1)
BEGIN
IF(NOT EXISTS(SELECT TOP(1) SPEED FROM #LIST))
BEGIN
INSERT #LIST(SPEED,RAM)
SELECT speed,ram
FROM VW_count
END
BREAK
END
SET #COUNT = (SELECT COUNT(SPEED) FROM #LIST)
WHILE #FOR <= #COUNT
BEGIN
SET #SPEED = (SELECT KETQUA.SPEED FROM (SELECT ROW_NUMBER() OVER (ORDER BY SPEED) AS STT, SPEED FROM #LIST) AS KETQUA WHERE KETQUA.STT = 1)
SET #RAM = (SELECT KETQUA.RAM FROM (SELECT ROW_NUMBER() OVER (ORDER BY RAM) AS STT, RAM FROM #LIST) AS KETQUA WHERE KETQUA.STT = 1)
IF #SPEED IS NULL
BEGIN
BREAK
END
ELSE
BEGIN
IF(EXISTS(SELECT speed FROM PC WHERE speed = #SPEED AND ram = #RAM))
BEGIN
INSERT #LISTMODEL(MODEL)
SELECT model FROM PC WHERE speed = #SPEED AND ram = #RAM
INSERT #RESULT(PC1,PC2)
SELECT DISTINCT L1.MODEL, L2.MODEL FROM #LISTMODEL AS L1 , #LISTMODEL AS L2
INSERT #RESULTREAL(COUPLE,SPEED,RAM)
SELECT CONCAT(R1.PC1, ', ', R1.PC2), #SPEED, #RAM FROM #RESULT R1 WHERE R1.PC1 > R1.PC2 OR NOT EXISTS (SELECT * FROM #RESULT R2 WHERE R2.PC1 = R1.PC2 AND R1.PC2 = R2.PC1)
END
DELETE #RESULT
DELETE #LISTMODEL
END
SET #FOR = #FOR + 1
DELETE TOP(1) FROM #LIST
CONTINUE
END
SELECT * FROM #RESULTREAL
It's normally a bad idea to use procedural statements when you could also use set-based logic.
You could make a SELECT query using a self-join on table [PC] based on the value of the [speed] and [ram] fields, but where the [model] value of the second table is larger than that of the first table. Something like this:
DECLARE #RESULTREAL TABLE(COUPLE NVARCHAR(20), SPEED INT, RAM INT);
INSERT INTO #RESULTREAL
SELECT
CAST(T1.[model] AS NVARCHAR) + N', ' + CAST(T2.[model] AS NVARCHAR),
T1.[speed],
T1.[ram]
FROM
[PC] AS T1
INNER JOIN [PC] AS T2 ON
T2.[speed] = T1.[speed] AND
T2.[ram] = T1.[ram]
WHERE
T2.[model] > T1.[model];

SQL - slow While loop

I have the following T-SQL code that is either adding or updating 1 record at a time to a temp table. Does anyone have any suggestions to speed this process up?
DECLARE #Total AS INT
SELECT #Total = count(AgentsID) from #TempAgentsConcat
DECLARE #counter AS INT
SET #counter = 1
DECLARE #CurrentVal AS NVARCHAR(1024)
DECLARE #RowCount AS INT
DECLARE #OBJ_ID AS INT
while (#counter <= #Total)
begin
SELECT #OBJ_ID = Id FROM #TempAgentsConcat WHERE AgentsId = #counter
SELECT #CurrentVal = SVRMachine FROM #TempAgentsConcat WHERE ID = #OBJ_ID
IF EXISTS (SELECT * FROM #TempEndpoints WHERE ID = #OBJ_ID)
BEGIN
UPDATE #TempEndpoints SET SVRMachine = #CurrentVal WHERE ID = #OBJ_ID
END
ELSE
BEGIN
INSERT INTO #TempEndpoints (SVRMachine, IPPort, ID)
VALUES (#CurrentVal, NULL, #OBJ_ID)
END
--END
SET #counter = #counter + 1
end
It looks like, you are trying to merge one table into other. First lets talk of couple of issues in your query-
1. Avoid using loops unless it's extremely necessary.
2. You are assigning two different variables by reading same row in 2 queries.
You can do this in single query like
SELECT #OBJ_ID = Id,#CurrentVal = SVRMachine FROM #TempAgentsConcat WHERE AgentsId = #counter
instead of 2 queries
SELECT #OBJ_ID = Id FROM #TempAgentsConcat WHERE AgentsId = #counter
SELECT #CurrentVal = SVRMachine FROM #TempAgentsConcat WHERE ID = #OBJ_ID
Let's rewrite the query without using loops. The answer by #Cetin is one of the solutions. Your requirement looks classic example of merging tables, so you may use SQL MERGE (SQL server 2008 and above). You can read more about MERGE, here, checkout the example 'C'.
Using MERGE, your query will look like below.
MERGE INTO #TempEndpoints AS Target
USING (SELECT SVRMachine , Id from #TempAgentsConcat)
AS Source (SVRMachine, ID)
ON Target.ID = Source.ID
WHEN MATCHED THEN
UPDATE SET SVRMachine = Source.SVRMachine
WHEN NOT MATCHED BY TARGET THEN
INSERT (ID, IPPort, SVRMachine) VALUES (Id, NULL,SVRMachine)
Why would you use a lot of variables and loop. SQL server (any SQL series database as well) works best with sets, rather than loops:
UPDATE #TempEndpoints
SET SVRMachine = ac.SVRMachine
FROM #TempAgentsConcat ac
WHERE #TempEndpoints.Id = ac.ID;
INSERT INTO #TempEndpoints
(
SVRMachine,
ID
)
SELECT SVRMachine,
ID
FROM #TempAgentsConcat ac
WHERE NOT EXISTS
(
SELECT * FROM #TempEndpoints ep WHERE ep.ID = ac.ID
);

Looking for missing gaps for getting "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS."

I have the following query to find missing gaps in the sort for each ModelID but I keep getting the following error and don't know why.
What I'm doing is in my first loop I am looping through the modelID's and in the inner loop I am looking for the missing gaps in the siSort column for that modelID and putting that into a temp table.
Msg 116, Level 16, State 1, Line 27
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
USE crm
GO
BEGIN
DECLARE #ID INT
DECLARE #MAXID INT
DECLARE #COUNT INT
DECLARE #iCustomListModelID INT
DECLARE #iCustomFieldID INT
DECLARE #MissingIds TABLE (ID INT)
DECLARE #Output TABLE (iCustomListModelID INT, siSort INT, iListItemID INT)
-- SELECT ALL DISTINCT ICustomListModelID's FROM CustomList Table
SELECT DISTINCT cl.iCustomListModelID
INTO #DistinctModelIDs
FROM dbo.CustomListModel clm
INNER JOIN dbo.CustomListType clt ON clm.iCustomListTypeID = clt.iCustomListTypeID
AND clt.vchCustomListTypeDescription = N'Household Custom Field'
INNER JOIN dbo.CustomList cl ON clm.iCustomListModelID = cl.iCustomListModelID
INNER JOIN dbo.CustomField cf ON cl.iListItemID = cf.iCustomFieldID
ORDER BY cl.iCustomListModelID
-- Get iCustomFieldID to insert into iListItemID
SET #iCustomFieldID = (SELECT * FROM dbo.CustomField cf WHERE vchLabel = '')
-- Begin Outer loop to go through each iCustomListModelID
WHILE (SELECT COUNT(iCustomListModelID) AS Total FROM #DistinctModelIDs) > 0
BEGIN
-- GRAB THE NEXT iCustomListModelID
SELECT #iCustomListModelID = (SELECT TOP 1 iCustomListModelID FROM #DistinctModelIDs);
DROP TABLE #List
SELECT siSort INTO #List FROM CustomList WHERE iCustomListModelID = #iCustomListModelID
SELECT #MAXID = siSort FROM dbo.CustomList WHERE iCustomListModelID = #iCustomListModelID
SET #ID = 1;
-- Inner loop to go through the missing gaps in siSort
WHILE #ID <= #MAXID
BEGIN
IF NOT EXISTS (
SELECT 'X' FROM #List WHERE siSort = #ID
)
INSERT INTO #MissingIDs (ID)
VALUES (#ID)
--INSERT THE MISSING ID INTO #outputTable Table
INSERT INTO #Output (iCustomListModelID, siSort, iListItemID)
VALUES (#iCustomListModelID, #ID, #iCustomFieldID)
SET #ID = #ID + 1;
END;
-- DELETE CURRENT iCustomListModelID
DELETE FROM #DistinctModelIDs WHERE iCustomListModelID = #iCustomListModelID
END
SELECT * FROM #Output
END;
One possibility is that the issue is this line:
SET #iCustomFieldID = (SELECT * FROM dbo.CustomField cf WHERE vchLabel = '')
If dbo.CustomerField doesn't have exactly one column (more than one column seems likely because vchLabel is already one column in the table), then this will generate an error of that type.

Use SQL Server recursive common table expression to get full path of all files in a folder(with subfolders)

There is a SQL Server undocumented extended stored procedure called xp_dirtree, which can return all files and folders name (include subfolders) in a table format. To practice my understanding of recursive CTE, I decide to use it to get the full path of all files in a specified folder(include subfolders). However, after an hour of head scratch I still can't figure out the correct way to do it. The following code is what I have currently. Can this purpose be implemented with recursive CTE?
DECLARE #dir NVARCHAR(260) ;
SELECT #dir = N'c:\temp' ;
IF RIGHT(#dir, 1) <> '\'
SELECT #dir = #dir + '\' ;
IF OBJECT_ID('tempdb..#dirtree', 'U') IS NOT NULL
DROP TABLE #dirtree ;
CREATE TABLE #dirtree
(
id INT PRIMARY KEY
IDENTITY,
subdirectory NVARCHAR(260),
depth INT,
is_file BIT
) ;
INSERT INTO #dirtree
EXEC xp_dirtree
#dir,
0,
1 ;
SELECT *
FROM #dirtree ;
WITH files
AS (
SELECT id,
subdirectory,
depth,
is_file, subdirectory AS path
FROM #dirtree
WHERE is_file = 1
AND depth <> 1
UNION ALL
-- ...
)
SELECT *
FROM files ;
Suppose the xp_dirtree output is:
/*
id subdirectory depth is_file
--- -------------- ------- -------
1 abc.mdf 1 1
2 a 1 0
3 a.txt 2 1
4 b.txt 2 1
5 a.rb 1 1
6 aaa.flv 1 1
*/
What I want is:
/*
path
------------------
c:\temp\abc.mdf
c:\temp\a\a.txt
c:\temp\a\b.txt
c:\temp\a.rb
c:\temp\aaa.flv
*/
If I understand you correct you want something like this:
Test data:
CREATE TABLE #dirtree
(
id INT,
subdirectory NVARCHAR(260),
depth INT ,
is_file BIT,
parentId INT
)
INSERT INTO #dirtree(id,subdirectory,depth,is_file)
VALUES
(1,'abc.mdf',1,1),(2,'a',1,0),(3,'a.txt',2,1),
(4,'b.txt',2,1),(5,'a.rb',1,1),(6,'aaa.flv',1,1)
Updated the parent id
UPDATE #dirtree
SET ParentId = (SELECT MAX(Id) FROM #dirtree
WHERE Depth = T1.Depth - 1 AND Id < T1.Id)
FROM #dirtree T1
Query
;WITH CTE
AS
(
SELECT
t.id,
t.subdirectory,
t.depth,
t.is_file
FROM
#dirtree AS t
WHERE
is_file=0
UNION ALL
SELECT
t.id,
CAST(CTE.subdirectory+'\'+t.subdirectory AS NVARCHAR(260)),
t.depth,
t.is_file
FROM
#dirtree AS t
JOIN CTE
ON CTE.id=t.parentId
)
SELECT
'c:\temp\'+CTE.subdirectory AS [path]
FROM
CTE
WHERE
CTE.is_file=1
UNION ALL
SELECT
'c:\temp\'+t.subdirectory
FROM
#dirtree AS t
WHERE
is_file=1
AND NOT EXISTS
(
SELECT
NULL
FROM
CTE
WHERE
CTE.id=t.id
)
Result
path
---------------
c:\temp\a\a.txt
c:\temp\a\b.txt
c:\temp\abc.mdf
c:\temp\a.rb
c:\temp\aaa.flv
EDIT
Changed the tables used in the example to more look like the ones in your question
/*
traverse directory tree and get back complete list of filenames w/ their paths
*/
declare
#dirRoot varchar(255)='\\yourdir'
declare
#sqlCmd varchar(255),
#idx int,
#dirSearch varchar(255)
declare #directories table(directoryName varchar(255), depth int, isfile int, rootName varchar(255),rowid int identity(1,1))
insert into #directories(directoryName, depth,isFile)
exec master.sys.xp_dirtree #dirRoot,1,1
if not exists(select * from #directories)
return
update #directories
set rootName = #dirRoot + '\' + directoryName
-- traverse from root directory
select #idx=min(rowId) from #directories
-- forever always ends too soon
while 1=1
begin
select #dirSearch = rootName
from #directories
where rowid=#idx
insert into #directories(directoryName, depth,isfile)
exec master.sys.xp_dirtree #dirSearch,1,1
update #directories
set rootName = #dirSearch + '\' + directoryName
where rootName is null
set #idx = #idx + 1
-- you see what i mean don't you?
if #idx > (select max(rowid) from #directories) or #idx is null
break
end
select
case isFile when 0 then 'Directory' else 'File' end [attribute],
rootName [filePath]
from #directories
order by filePath
Almost nine years later, and unfortunately there's no out-of-the-box solution that I know of. So I'm still looking into xp_dirtree and need a solution to this.
I gave Arion's answer a try and found that it was producing results. However, with a large file system of more than 11K objects, it was running very slowly. I saw that it was very slow even from the get go with:
UPDATE #dirtree
SET ParentId = (SELECT MAX(Id) FROM #dirtree
WHERE Depth = T1.Depth - 1 AND Id < T1.Id)
FROM #dirtree T1
Although this isn't an islands-and-gaps problem, it has some similarities and the kind of thinking with those problems has helped me here. The code at the end is my stored procedure. The sections have some comments as to what the code is doing.
You would use it like this:
exec directory
#root = 'c:\somepath',
#depth = 3,
#outputTable = '##results';
select * from ##results;
Which results in output like:
+---------------------------------+------------+------------+-----------+--------+-----------+----------+
| path | name | nameNoExt | extension | isFile | runtimeId | parentId |
+---------------------------------+------------+------------+-----------+--------+-----------+----------+
| c:\somePath\DataMovers | DataMovers | DataMovers | NULL | 0 | 4854 | NULL |
| c:\somePath\DataMovers\main.ps1 | main.ps1 | main | ps1 | 1 | 4859 | 4854 |
+---------------------------------+------------+------------+-----------+--------+-----------+----------+
I had to build it this way because internally it takes xp_dirtree output and loads it into a temp table. This prevents the ability to take the results of the proc and load them into a table outside of the proc because of the ban on nested insert-exec statements. Don't expose #outputTable to untrusted users because it is susceptible to sql-injection. Of course re-work the proc to avoid this however it meets your needs.
/*
Summary: Lists file directory contents.
Remarks: - stackoverflow.com/q/10298910
- This assumes that the tree is put in order where
subfolders are listed right under their parent
folders. If this changes in the future, a
different logic will need to be implemented.
Example: exec directory 'c:\somepath', 3, '##results';
select * from ##results;
*/
create procedure directory
#root nvarchar(255),
#depth int,
#outputTable sysname
as
-- initializations
if #outputTable is null or not (left(#outputTable,2) = '##') or charindex(' ', #outputTable) > 0
throw 50000, '#outputTable must be a global temp table with no spaces in the name.', 1;
if exists (select 0 from tempdb.information_schema.tables where table_name = #outputTable)
begin
declare #msg nvarchar(255) = '''tempdb.dbo.' + #outputTable + ''' already exists.';
throw 50000, #msg, 1;
end
-- fetch the tree (it doesn't have full path names)
drop table if exists #dir;
create table #dir (
id int identity(1,1),
parentId int null,
path nvarchar(4000),
depth int,
isFile bit,
isLeader int default(0),
groupId int
)
insert #dir (path, depth, isFile)
exec xp_dirtree #root, #depth, 1;
-- identify the group leaders (based on a change in depth)
update d
set isLeader = _isLeader
from (
select id,
isLeader,
_isLeader = iif(depth - lag(depth) over(order by id) = 0, 0, 1)
from #dir
) d;
-- find the parents for each leader (subsetting just for leaders improves efficiency)
update #dir
set parentId = (
select max(sub.id)
from #dir sub
where sub.depth = d.depth - 1
and sub.id < d.id
and d.isLeader = 1
)
from #dir d
where d.isLeader = 1;
-- assign an identifier to each group (groups being objects that are 'siblings' of the leader)
update d
set groupId = _groupId
from (
select *, _groupId = sum(isLeader) over(order by id)
from #dir
) d;
-- set the parent id for each item based on the leader's parent id
update d
set d.parentId = leads.parentId
from #dir d
join #dir leads
on d.groupId = leads.groupId
and leads.parentId is not null;
-- convert the path names to full path names and calculate path parts
drop table if exists #pathBuilderResults;
with pathBuilder as (
select id, parentId, origId = id, path, pseudoDepth = depth
from #dir
union all
select par.id,
par.parentId,
pb.origId,
path = par.path + '\' + pb.path,
pseudoDepth = pb.pseudoDepth - 1
from pathBuilder pb
join #dir par on pb.parentId = par.id
where pb.pseudoDepth >= 2
)
select path = #root + '\' + pb.path,
name = d.path,
nameNoExt = iif(ext.value is null, d.path, left(d.path, len(d.path) - len(ext.value) - 1)),
extension = ext.value,
d.isFile,
runtimeId = pb.origId,
parentId = d.parentId
into #pathBuilderResults
from pathBuilder pb
join #dir d on pb.origId = d.id
cross apply (select value = charindex('.', d.path)) dotPos
cross apply (select value = right(d.path, len(d.path) - dotPos.value)) pseudoExt
cross apply (select value = iif(d.isFile = 1 and dotPos.value > 0, pseudoExt.value, null)) ext
where pb.pseudoDepth = 1
order by pb.origId;
-- terminate
declare #sql nvarchar(max) = 'select * into ' + #outputTable + ' from #pathBuilderResults';
exec (#sql);
Create and use sp_dirtree #Path = 'c:\', #FileOnly = 1
create or alter proc sp_dirtree
#Path nvarchar(4000)
, #Depth int = 0
, #FileOnly bit = 0
as -- Dir tree with fullpath. sergkog 2018-11-14
set nocount on
declare #Sep nchar(1) = iif(patindex('%/%',#Path) > 0,'/','\') -- windows or posix
set #Path += iif(right(#Path,1) <> #Sep, #Sep,'')
declare #dirtree table(
Id int identity(1,1)
, subdirectory nvarchar(4000) not null
, depth int not null
, is_file bit not null
, parentId int null
)
insert #dirtree(subdirectory, depth, is_file)
exec xp_dirtree #Path, #Depth, 1
update #dirtree
set ParentId = (select max(id) from #dirtree where Depth = t1.Depth - 1 and Id < t1.Id)
from #dirtree t1
;with cte as(
select t.*
from #dirtree t
where is_file=0
union all
select t.id
, convert(nvarchar(4000), cte.subdirectory+ #Sep + t.subdirectory)
, t.depth
, t.is_file
, t.parentId
from
#dirtree t join cte on cte.id = t.parentId
)
select #Path + cte.subdirectory as FullPath
, cte.is_file as IsFile
from cte
where cte.is_file = iif(#FileOnly = 1, 1,cte.is_file)
union all
select #Path + t.subdirectory
, t.is_file
from #dirtree t
where
t.is_file = iif(#FileOnly = 1, 1,t.is_file)
and not exists(select null from cte
where cte.id=t.id
)
order by FullPath, IsFile
go

Trigger to prevent infinite loop in a sql tree

I have node table with (NodeId, NodeName) and structure table (ParentNodeId, ChildNodeId). How can I write a trigger that check if an insert update or delete statement can cause infinite relation?
Here is my solution, and so far it works as expected.
CREATE TRIGGER [dbo].[CheckNodeDependence] ON [dbo].[ObjectTrees]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
DECLARE #CTable TABLE(ChildId INT NOT NULL,
ParentId INT NOT NULL,
[Level] INT NOT NULL,
RowId INT NOT NULL)
DECLARE #Level INT
SET #Level = 1
DECLARE #rows_affected INT
SET #rows_affected = 1
INSERT INTO #CTable
SELECT ObjectId, ParentId, 1, ObjectId FROM INSERTED
WHILE #rows_affected > 0
BEGIN
SET #Level = #Level + 1
INSERT INTO #CTable
SELECT T.ObjectId, T.ParentId, #Level, C.RowId
FROM ObjectTrees T
INNER JOIN #CTable C ON T.ParentId = C.ChildId
AND C.Level = #Level - 1
SET #rows_affected = ##rowcount
IF EXISTS(
SELECT * FROM #CTable B
INNER JOIN #CTable V ON B.level = 1
AND V.Level > 1
AND V.RowId = B.RowId
AND V.ChildId = B.RowId)
BEGIN
DECLARE #error_message VARCHAR(200)
SET #error_message = 'Operation would cause illegal circular reference in tree structure, level = ' + CAST(#Level AS VARCHAR(30))
RAISERROR(#error_message,16,1)
ROLLBACK TRANSACTION
RETURN
END
END
END
GO
You'll have to recursively check the circular dependency condition in which the parent doesn't become a child of its own child, either directly or indirectly.
In SQL Server 2005, you can write a recursive CTE for the same. An example -
WITH [RecursiveCTE]([Id], [ParentAccountId]) AS
(
SELECT
[Id],
[ParentAccountId]
FROM [Structure]
WHERE [Id] = #Id
UNION ALL
SELECT
S.[Id],
S.[ParentAccountId]
FROM [Structure] S INNER JOIN [RecursiveCTE] RCTE ON S.[ParentAccountId] = RCTE.[Id]
)
SELECT * FROM [RecursiveCTE]