SQL - Clone a record and its descendants - sql

I would like to be able to clone a record and its descendants in the same table. An example of my table would be the following:
Table1
id | parentid | name
---------------------
1 | 0 | 'Food'
2 | 1 | 'Taste'
3 | 1 | 'Price'
4 | 2 | 'Taste Requirements'
The "id" column is the primary key and auto-increments. The 'Food' record (i.e. where id = 1) has two records underneath it called 'Taste' and 'Price'. The 'Taste' record has a record underneath it called 'Taste Requirements'. I would like to be able to clone the 'Food' record so that Table1 would look like the following:
Table1
id | parentid | name
---------------------
1 | 0 | 'Food'
2 | 1 | 'Taste'
3 | 1 | 'Price'
4 | 2 | 'Taste Requirements'
5 | 0 | 'Cookies'
6 | 5 | 'Taste'
7 | 5 | 'Price'
8 | 6 | 'Taste Requirements'
(where 'Cookies' is the name of the new category that I want to create). I am able to select all the descendants of 'Food' using:
with Table1_CTE( id, parentid, name )
as
(
select t.id, t.parentid, t.name from Table1 t
where t.id = 1
union all
select t.id, t.parentid,t. name from Table1 t
inner join Table1_CTE as tc
on t.parentid = tc.id
)
select id, parentid, name from Table1_CTE
and I am able to clone just the 'Food' record (i.e. where id = 1) using:
insert into Table1 ( parentid, name )
select ( parentid, 'Cookies' )
from Table1 where id = 1
but I am having problems trying to combine the two queries to clone the descendants of 'Food'. Also, I am trying to avoid using stored procedures, triggers, curosrs, etc. Is what I am trying to do possible? I have seen some examples on the web but have been unable to apply them to my requirements.

As Martin suggested, you need to enable IDENTITY_INSERT so that you can push your own identity values. You may also need to acquire a table lock to ensure that Max( Id ) returns the correct value.
If object_id('tempdb..#TestData') is not null
Drop Table #TestData
GO
Create Table #TestData
(
Id int not null identity(1,1) Primary Key
, ParentId int not null
, Name varchar(50) not null
)
GO
Set Identity_Insert #TestData On
GO
Insert #TestData( Id, ParentId, Name )
Values( 1,0,'Food' )
, ( 2,1,'Taste' )
, ( 3,1,'Price' )
, ( 4,2,'Taste Requirement' );
With Data As
(
Select Cast(MaxId.Id + 1 As int) As Id
, T.ParentId
, 'Copy Of ' + T.name As Name
, T.Id As OldId
, 0 As OldParentId
From #TestData As T
Cross Join( Select Max( id ) As Id From #TestData ) As MaxId
Where T.Name = 'Food'
Union All
Select Cast(Parent.id + Row_Number() Over( Order By Child.Id ) + 1 As int)
, Parent.Id
, 'Copy of ' + Child.Name
, Child.Id
, Child.ParentId
From Data As Parent
Join #TestData As Child
On Child.ParentId = Parent.OldId
)
Insert #TestData( Id, ParentId, Name )
Select Id, ParentId, Name
From Data
GO
Set Identity_Insert #TestData Off
GO
Results
id | parentid | name
-- | -------- | -----------------
1 | 0 | Food
2 | 1 | Taste
3 | 1 | Price
4 | 2 | Taste Requirement
5 | 0 | Copy Of Food
7 | 5 | Copy of Taste
8 | 5 | Copy of Price
9 | 7 | Copy of Taste Requirement

Assuming your CTE picks a root record and all it's descendents (it didn't seem to when I reproduced using your data above), then you can clone all selected records and insert like this:
with Table1_CTE( id, parentid, name )
as
(
select t.id, t.parentid, t.name from Table1 t
where c.icategoryid = 1
union all
select t.id, t.parentid,t. name from Table1
inner join Table1_CTE as tc
on t.parentid = tc.id
)
insert into dbo.testinsertheirarchy ( parentid, name )
select parentid, name from Table1_CTE

Related

Want to know all possible Parent and Child Rows against specific Id?

How to know all possible Parent and Child Rows against specific Id?
e.g. have following table:
MyTable:
-----------------------------------------------------
| Id | PId | Description |
-----------------------------------------------------
| 1 | NULL | A is Parent |
| 2 | 1 | B is Child of A |
| 3 | 2 | C is Child of B |
| 4 | NULL | D is Parent |
| 5 | NULL | E is Parent |
| 6 | 5 | F is Child of E |
-----------------------------------------------------
want to know all possible parent and child when pass spesific id
e.g.
CASE-01:
When #MyLookupId=2 OR #MyLookupId=1 OR #MyLookupId=3 One of from them Then Result Should Be,
-------
| Id |
-------
| 1 |
| 2 |
| 3 |
-------
CASE-02:
When #MyLookupId=4 Then Result Should Be,
-------
| Id |
-------
| 4 |
-------
CASE-03:
When #MyLookupId=6 Then Result Should Be,
-------
| Id |
-------
| 5 |
| 6 |
-------
Here is SQL for table:
IF OBJECT_ID('tempdb.dbo.#MyTable', 'U') IS NOT NULL DROP TABLE #MyTable;
SELECT * INTO #MyTable FROM (
SELECT (1)Id, (NULL)PId, ('A IS Parent')Description UNION ALL
SELECT (2)Id, (1)PId, ('B IS Child of A')Description UNION ALL
SELECT (3)Id, (2)PId, ('C IS Child of B')Description UNION ALL
SELECT (4)Id, (NULL)PId, ('D IS Parent')Description UNION ALL
SELECT (5)Id, (NULL)PId, ('E IS Parent')Description UNION ALL
SELECT (6)Id, (5)PId, ('F IS Child of E')Description ) AS tmp
SELECT * FROM #MyTable
You could use recursive cte
-- temp returns full tree of each rootId (parentid = null)
;WITH temp AS
(
SELECT sd.Id, sd.PId, sd.Id AS RootId
FROM #MyTable sd
WHERE sd.PId IS NULL
UNION ALL
SELECT sd.Id, sd.PId, t.RootId
FROM temp t
INNER JOIN #MyTable sd ON t.Id = sd.PId
)
SELECT t2.Id
FROM temp t
INNER JOIN temp t2 ON t2.RootId = t.RootId
WHERE t.Id = #Id
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/RAITMT72805
The answer given by TriV works, but requires a calculation of the entire hierarchy of your source table each time the query is run, which may not perform well at larger scale.
A more narrow approach is to find the Parent and Child records that only relate to the ID you are searching for:
declare #t table(ID int, PID int);
insert into #t values(1,null),(2,1),(3,2),(4,null),(5,null),(6,5);
declare #ID int = 2;
with c as
(
select ID
,PID
from #t
where ID = #ID
union all
select t.ID
,t.PID
from #t t
join c
on(t.PID = c.ID)
)
,p as
(
select ID
,PID
from #t
where ID = #ID
union all
select t.ID
,t.PID
from #t t
join p
on(t.ID = p.PID)
)
select ID
from p
union all
select ID
from c
where c.ID <> #ID
order by ID;
Output:
ID
````
1
2
3

SQL group with Recursive CTE

I'm working on SQL Server 2008. I believe the answer to my Q lies in a recursive CTE but any solution would be greatly appreciated.
In the sam_DB.dbo.example table below where the PID is not null it links back to an ID
ID | PID
------ | ------
1 | NULL
2 | 1
3 | 2
4 | 3
5 | NULL
6 | 5
7 | 6
8 | NULL
9 | NULL
10 | 9
I want my output to have a new field (CID) that identifies each record in a chain of linkages from PID to ID as part of a group, as per below.
ID | PID | CID
------ | ------ | ------
1 | NULL | 1
2 | 1 | 1
3 | 2 | 1
4 | 3 | 1
5 | NULL | 2
6 | 5 | 2
7 | 6 | 2
8 | NULL | 3
9 | NULL | 4
10 | 9 | 4
You're correct that you'd need a CTE for this.
You need to define the first part of the query to select the top level records (i.e. those which have no parent):
select ID, PID, ID
from #t
where PID is null
Then, for every row added to the resulting CTE (i.e. first of all for those records returned by the above query, then again for each new row added by this second part of the query, repeated for each addition until no new additions are made) you should add all records from the source table for which the parent id matches the previously added row's id.
select t.ID, t.PID, c.CID
from cte c
inner join #t t
on t.PID = c.ID
Aside from this logic, the only other thing to be aware of is that the CID column for the first expression takes the record's ID, whilst for those records returned by the second expression it takes the parent record's CID.
Full Code
--set up the demo data
declare #t table (ID int not null, PID int null)
insert #t
values (1, null)
, (2,1)
, (3,2)
, (4,3)
, (5,null)
, (6,5)
, (7,6)
, (8,null)
, (9,null)
, (10,9)
--actual demo
;with cte (ID, PID, CID) as (
--select out top most (ancestor) records; setting CID to ID (since they're the oldest ancestor in their own chain, given they don't have parents)
select ID, PID, ID
from #t
where PID is null
union all
--select each record that is a child of the record we previously selected, holding the ancestor as the parent record's ancestor
select t.ID, t.PID, c.CID
from cte c
inner join #t t
on t.PID = c.ID
)
select *
from CTE
order by ID
you have to use Common text expression With Row_Number Window function
CREATE TABLE #TblTemp(ID int,PID int)
INSERT INTO #TblTemp(ID ,PID ) VALUES (1,NULL),(2,1),(3,1),(4,3),(5,NULL),(6,5),(7,6),(8,NULL),(9,NULL),(10,9)
;WITH CTE (ID, PID, CID) AS (
SELECT ID, PID, ROW_NUMBER() OVER(ORDER BY ID) RN
FROM #TBLTEMP
WHERE PID IS NULL
UNION ALL
SELECT T.ID, T.PID, C.CID
FROM CTE C
INNER JOIN #TBLTEMP T
ON T.PID = C.ID
)
SELECT *
FROM CTE
ORDER BY ID
I will post some simple example
-- shows how to create a recursive grouping for wrongly linked or corrupted pieces of a parent/child groups
declare #t table (item varchar(2), tr int null, rc int null)
insert #t select 'a',1,9 -- no links 'a' - is group parent
insert #t select 'b',2,1 -- links to 'a'
insert #t select 'c',3,2 -- links to 'b'
insert #t select 'd',4,3 -- links to 'd'
insert #t select 'e',6,7 -- no links 'e' - is a different group
insert #t select 'f',8,2 -- links to 'c'
-- grn-group name based on a parent item name;
-- gid-group name based on a parent item id;
-- tr-transactionID ; rc-recursiveID;
-- rc_New-new recursiveID to use; rc_Old - original recursiveID
;with cte as
(
select grn=s.item, gid=s.tr, s.item, s.tr, rc_New= t.tr, rc_Old=s.rc from #t s
left join #t t on t.tr=s.rc where (t.tr is NULL or s.rc is NULL)
union all
select c.grn, c.gid,s.item, s.tr, rc_New=s.rc, rc_Old=s.rc
from cte c join #t s on s.rc=c.tr where s.rc is not NULL
)
select * from cte order by 2,3
option (MAXRECURSION 32767, FAST 100)

How to get detail table records as string in SQL Server

I have a table in database which contains hospital names as follow:
+------------+--------------+
| HospitalID | HospitalName |
+------------+--------------+
| 1 | Hosp1 |
| 2 | Hosp2 |
| 3 | Hosp3 |
| 4 | Hosp4 |
+------------+--------------+
Another table also exist which contains activity names as follows:
+------------+--------------+
| ActivityID | ActivityName |
+------------+--------------+
| 1 | Act1 |
| 2 | Act2 |
| 3 | Act3 |
| 4 | Act4 |
| 5 | Act5 |
+------------+--------------+
There's a N*M relation between these tables, i.e. each hospital can operate different activities. Therefore another table is required as follows:
+----+------------+------------+
| ID | HospitalID | ActivityID |
+----+------------+------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 5 |
| 4 | 2 | 1 |
| 5 | 2 | 3 |
| 6 | 3 | 2 |
+----+------------+------------+
I want to write a select statement which selects hospital names and their related activities in a string field as follows:
+--------------+------------------+
| HospitalName | ActivityNames |
+--------------+------------------+
| Hosp1 | Act1, Act2, Act5 |
| Hosp2 | Act1, Act3 |
| Hosp3 | Act2 |
| Hosp4 | |
+--------------+------------------+
I have written the select statement using a function for ActivityNames field using a cursor but it is not optimized and the system performance decreases as the number of records increases.
Any solution or suggestion on how to solve this problem?
You can do this just with a select. No need of looping or Cursor for this. Looping will make performance degrade.
So the Schema will be
CREATE TABLE #HOSPITAL( HOSPITALID INT, HOSPITALNAME VARCHAR(20))
INSERT INTO #HOSPITAL
SELECT 1, 'HOSP1'
UNION ALL
SELECT 2 , 'HOSP2'
UNION ALL
SELECT 3 ,'HOSP3'
UNION ALL
SELECT 4 , 'HOSP4'
CREATE TABLE #ACTIVITY( ActivityID INT, ActivityName VARCHAR(50) )
INSERT INTO #ACTIVITY
SELECT 1, 'Act1'
UNION ALL
SELECT 2, 'Act2'
UNION ALL
SELECT 3, 'Act3'
UNION ALL
SELECT 4, 'Act4'
UNION ALL
SELECT 5, 'Act5'
CREATE TABLE #HOSPITAL_ACT_MAP(ID INT, HospitalID INT, ActivityID INT)
INSERT INTO #HOSPITAL_ACT_MAP
SELECT 1, 1, 1
UNION ALL
SELECT 2, 1, 2
UNION ALL
SELECT 3, 1, 5
UNION ALL
SELECT 4, 2, 1
UNION ALL
SELECT 5, 2, 3
UNION ALL
SELECT 6, 3, 2
And do Select like below with CTE
;WITH CTE AS (
SELECT DISTINCT H.HOSPITALNAME, A.ActivityName FROM #HOSPITAL_ACT_MAP HA
INNER JOIN #HOSPITAL H ON HA.HospitalID = H.HOSPITALID
INNER JOIN #ACTIVITY A ON HA.ActivityID = A.ActivityID
)
SELECT HOSPITALNAME
, (SELECT STUFF((SELECT ','+ActivityName FROM CTE C1
WHERE C1.HOSPITALNAME = C.HOSPITALNAME
FOR XML PATH('')),1,1,''))
FROM CTE C
GROUP BY HOSPITALNAME
Edit from Comments
If you can't use CTE and Stuff go for Method 2
DECLARE #TAB TABLE (HOSPITALNAME VARCHAR(20),ActivityName VARCHAR(20) )
INSERT INTO #TAB
SELECT DISTINCT H.HOSPITALNAME, A.ActivityName FROM #HOSPITAL_ACT_MAP HA
INNER JOIN #HOSPITAL H ON HA.HospitalID = H.HOSPITALID
INNER JOIN #ACTIVITY A ON HA.ActivityID = A.ActivityID
SELECT HOSPITALNAME, SUBSTRING(ACTIVITIES,1, LEN(ACTIVITIES)-1) FROM(
SELECT DISTINCT HOSPITALNAME,(SELECT ActivityName+',' FROM #TAB T1
WHERE T1.HOSPITALNAME = T.HOSPITALNAME
FOR XML PATH('') ) AS ACTIVITIES FROM #TAB T
)A
Note: For the performance purpose I have stored the Intermediate result on #TAB (Table variable). If you want you can directly Query it with Sub Query.
using STUFF function to achieve your result :
CREATE TABLE #Hospital(HospitalID INT,HospitalName VARCHAR(100))
CREATE TABLE #Activity(ActivityID INT,ActivityName VARCHAR(100))
CREATE TABLE #RelationShip(Id INT,HospId INT,ActId INT)
CREATE TABLE #ConCat(HospitalID INT ,HospName VARCHAR(100), ActName
VARCHAR(100),UpFlag TINYINT DEFAULT(0))
DECLARE #HospId INT = 0,#String VARCHAR(200) = ''
INSERT INTO #Hospital(HospitalID ,HospitalName )
SELECT 1,'Hosp1' UNION ALL
SELECT 2,'Hosp2' UNION ALL
SELECT 3,'Hosp3' UNION ALL
SELECT 4,'Hosp4'
INSERT INTO #Activity(ActivityID ,ActivityName )
SELECT 1,'Act1' UNION ALL
SELECT 2,'Act2' UNION ALL
SELECT 3,'Act3' UNION ALL
SELECT 4,'Act4' UNION ALL
SELECT 5,'Act5'
INSERT INTO #RelationShip(ID,HospId,ActId)
SELECT 1 , 1 , 1 UNION ALL
SELECT 2 , 1 , 2 UNION ALL
SELECT 3 , 1 , 5 UNION ALL
SELECT 4 , 2 , 1 UNION ALL
SELECT 5 , 2 , 3 UNION ALL
SELECT 6 , 3 , 2
SELECT HospitalName , STUFF( ( SELECT ',' + ActivityName FROM #Activity
JOIN #RelationShip ON ActId = ActivityID WHERE HospId = HospitalID FOR XML
PATH('') ),1,1,'')
FROM #Hospital
GROUP BY HospitalID,HospitalName
***FOR SQLServer2005 Use below code***
INSERT INTO #ConCat (HospitalID ,HospName)
SELECT DISTINCT HospitalID ,HospitalName
FROM #Hospital
WHILE EXISTS(SELECT 1 FROM #ConCat WHERE UpFlag = 0)
BEGIN
SELECT #HospId = HospitalID FROM #ConCat WHERE UpFlag = 0 ORDER BY
HospitalID
SET #String = ''
SELECT #String = ISNULL(#String,'') + CAST(A.ActivityName AS VARCHAR) +
',' FROM
(
SELECT ActivityName
FROM #RelationShip
JOIN #Activity ON ActId = ActivityID
WHERE HospId = #HospId
) A
UPDATE #ConCat SET UpFlag = 1,ActName = CASE WHEN #String = '' THEN
#String ELSE SUBSTRING(#String,0,LEN(#String) ) END WHERE HospitalID
= #HospId
END
SELECT * FROM #ConCat
You can use json to increase the performance of front end.
There is no particular solution if you are using open source databases.
Try to use IBM db2 or ORACLE database to ensure performance of your app.
then generate json data . You will find the improvement in speed

How to create a condition for this case?

Sample Table:
Id |Acc_Code|Description |Balance | Acclevel| Acctype| Exttype|
--- -------- ----------------- |-------- |-------- | -------| -------|
1 |SA |Sales | 0.00 | 1 | SA | |
2 |CS |Cost of Sales | 0.00 | 1 | CS | |
3 |5000/001|Revenue | 94.34 | 2 | SA | |
4 |5000/090|Sales(Local) | 62.83 | 2 | SA | |
5 |7000/000|Manufacturing Acc |-250.80 | 2 | CS | MA |
6 |7000/200|Manufacturing Acc | 178.00 | 2 | CS | |
This is a sample data of a temporary table which would be used to be inserted into another temporary table that would calculate the data for Profit and Loss Statement (For Manufacturing related Accounts only).
In this case, the acc_code for Manufacturing accounts start from 7000/000 and separated/partitioned for each following Exttype.
Eg: We start from the exttype of MA and based on its acclevel (could be 2 or more) until the next exttype.
The idea is we get the manufacturing accounts by SELECT FROM tmp_acc_list WHERE acc_code BETWEEN #start_acc_code (7000/000 in this case) AND #end_acc_code (the data before the next exttype)
I don't know what the exttype is, I'm still learning the tables.
How do we create the #end_acc_code part out from this sample table?
So here is a all in one script.
I created Your table for test:
create table #tmp_acc_list(
Id numeric,
Acc_Code nvarchar(100),
Acclevel numeric,
Acctype nvarchar(100),
Exttype nvarchar(100));
GO
insert into #tmp_acc_list(Id, Acc_Code, Acclevel, Acctype, Exttype)
select 1 , 'SA', 1,'SA', null union all
select 2 , 'CS', 1,'CS', null union all
select 3 , '5000/001', 2,'SA', null union all
select 4 , '5000/090', 2,'SA', null union all
select 5 , '7000/000', 2,'CS', 'MA' union all
select 6 , '7000/200', 2,'CS', null
;
Then comes the query:
with OrderedTable as -- to order the table is Id is not an order
(
select
t.*, ROW_NUMBER() over (
order by id asc --use any ordering You need here
)
as RowNum
from
#tmp_acc_list as t
),
MarkedTable as -- mark with common number
(
select
t.*,
Max(case when t.Exttype is null then null else t.RowNum end)
over (order by t.RowNum) as GroupRownum
from OrderedTable as t
),
GroupedTable as -- add group Exttype
(
select
t.Id, t.Acc_Code, t.Acclevel, t.Acctype, t.Exttype,
max(t.Exttype) over (partition by t.GroupRownum) as GroupExttype
from MarkedTable as t
)
select * from GroupedTable where GroupExttype = 'MA'
Is this what You need?
select *
from
(
select Id, Acc_Code
from tmp_acc_list
where Acc_Code = '7000/000'
) s
cross join tmp_acc_list a
cross apply
(
select top 1 x.Id, x.Acc_Code
from tmp_acc_list x
where x.Id >= a.Id
and x.AccLevel = a.AccLevel
and x.Acctype = a.Acctype
and x.Exttype = ''
order by Id desc
) e
where a.Id between s.Id and e.Id

How to use recursive logic to return only the Root row in a sql table (SQL Server 2008 R2)

This is for SQL Server 2008 R2, I'm a novice at SQL so please be as specific as you can.
Table1 has some recursive structure built into it, where the ParentId is either Null meaning it's the root, or ParentId is the Id of another row in Table1 which denotes it as a child.
Example data set:
Table1Id ParentId
--------------------------------------------
1 NULL
2 1
3 1
4 2
5 NULL
6 2
7 6
8 NULL
9 8
With the above example the table then has the following tree structure with 3 root nodes:
Root 1 5 8
Child(teir1) 2 3 9
Child(teir2) 4 6
Child(tier3) 7
....
Is there a way to return only the Root row given any of the row Ids? For example:
InputId ReturnedRowId
----------------------------
1 1
2 1
3 1
4 1
5 5
6 1
7 1
8 8
9 8
Any help would be appreciated.
You can use a CTE and traverse the hierarchy
IF OBJECT_ID('tempdb..#testData') IS NOT NULL
DROP TABLE #testData
CREATE TABLE #testData (
Table1Id INT
,ParentId INT NULL
)
INSERT INTO #testData ( Table1Id, ParentId )
VALUES
(1, NULL )
,(2, 1 )
,(3, 1 )
,(4, 2 )
,(5, NULL )
,(6, 2 )
,(7, 6 )
,(8, NULL )
,(9, 8 )
DECLARE #InputId INT
SET #InputId = 2 --<<--Change this as appropriate
;WITH cteTraverse
AS
(
SELECT
T.Table1Id, T.ParentId
FROM
#testData T
WHERE
Table1Id = #InputId
UNION ALL
SELECT
T1.Table1Id, T1.ParentId
FROM
#testData T1
INNER JOIN
cteTraverse T2 ON T1.Table1Id = T2.ParentId
)
SELECT
#InputId '#InputId', Table1Id 'ReturnedRowId'
FROM
cteTraverse
WHERE
ParentId IS NULL
This query do the job.
with CTE as
(
Select Table1ID as ID, Table1ID as Ancestor, 0 as level
from Table1
UNION ALL
Select ID, ParentID, level + 1
from Table1
inner join CTE on CTE.Ancestor = Table1.Table1ID
where ParentID is not NULL
)
,
R_only as
(
Select ID as ID, MAX(level) as max_level
from CTE
group by ID
)
select CTE.ID, Ancestor
from CTE inner join R_only on CTE.ID = R_only.ID and CTE.level = R_only.max_level
order by CTE.ID
Here's a script that finds the root nodes for the nodes table you have. What it does:
In the first CTE, recurses the nodes until the parent is found. Recursion keeps track of the depth in column level.
In a second CTE, determines the maximum depth for each node: max_level. This is the depth where the parent was determined.
Select the nodes with maximum depth.
CREATE TABLE #tree(table1_id INT PRIMARY KEY,parent_id INT);
INSERT INTO #tree(table1_id,parent_id)VALUES
(1,NULL),(2,1),(3,1),(4,2),(5,NULL),(6,2),(7,6),(8,NULL),(9,8);
;WITH cte_tr AS (
SELECT table1_id, parent_id, level=0
FROM #tree
UNION ALL
SELECT t_c.table1_id, t_p.parent_id, level=t_c.level+1
FROM cte_tr AS t_c
INNER JOIN #tree AS t_p ON
t_p.table1_id=t_c.parent_id
WHERE t_p.parent_id IS NOT NULL
),
cte_ml AS (
SELECT table1_id, max_level=MAX(level)
FROM cte_tr
GROUP BY table1_id
)
SELECT cte_tr.table1_id, root_node=ISNULL(cte_tr.parent_id,cte_tr.table1_id)
FROM cte_tr
INNER JOIN cte_ml ON
cte_ml.table1_id=cte_tr.table1_id AND
cte_ml.max_level=cte_tr.level
ORDER BY cte_tr.table1_id
DROP TABLE #tree;
Result:
+-----------+-----------+
| table1_id | root_node |
+-----------+-----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 5 |
| 6 | 1 |
| 7 | 1 |
| 8 | 8 |
| 9 | 8 |
+-----------+-----------+
Use start with and connect by prior. Check this documentation here
http://psoug.org/reference/connectby.html
I am not sure if "connect by" is available in other databases other than Oracle. Check it out.
You could also try with clause. Someone has tried here.
Simulation of CONNECT BY PRIOR of ORACLE in SQL SERVER
With works somewhat like this
with query1 as (select .... from .... where ....),
query2 as (select .... from .... where ....)
select ...
from query1 q1,
query2 q2
where q1.xxxxx = q2.xxxx