Find the highest grandparent on self-referencing table SQL Server - sql

I have this table in SQL Server:
Parent Child
1 2
89 7
2 3
10 5
3 4
I need to build a recursive Stored Procedure that finds the maximum ascendant of any child.
For example: If I want to find the maximum ascendant of 4, it should return 1 because:
4 is the child of 3.
3 is the child of 2.
2 is the child of 1.
So I can find the ultimate parent.

A perfect job for recursive CTE:
;WITH
cte1 AS
( -- Recursively build the relationship tree
SELECT Parent
, Child
, AscendentLevel = 1
FROM my_table
UNION ALL
SELECT t.Parent
, cte1.Child
, AscendentLevel = cte1.AscendentLevel + 1
FROM cte1
INNER JOIN my_table t ON t.Child = cte1.Parent
),
cte2 AS
( -- Now find the ultimate parent
SELECT Parent
, Child
, rn = ROW_NUMBER() OVER (PARTITION BY Child ORDER BY AscendentLevel DESC)
FROM cte1
)
SELECT *
FROM cte2
WHERE rn = 1
OPTION (MAXRECURSION 0)

WITH CTE_myTable AS (
SELECT Id, ParentId, NULL As RootParent, 1 As Lvl
FROM dbo.myTable
UNION ALL
SELECT a.id, b.ParentId, a.ParentId As RootParent, Lvl + 1
FROM CTE_myTable AS a INNER JOIN
dbo.myTable AS b ON a.ParentId = b.Id
)
, CTE_myTable_RN AS (
SELECT Id, RootParent, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Lvl DESC) RN
FROM CTE_myTable
)
, JoinParentChild As (
SELECT Id, ISNULL(RootParent, Id) As RootParent
FROM CTE_myTable_RN
WHERE RN = 1
)
SELECT TOP(100) PERCENT JoinParentChild.Id, JoinParentChild.RootParent
FROM JoinParentChild
ORDER BY JoinParentChild.Id

Related

SQL Self Recursive Join with Grouping

In SQL Server, I have the following table:
Name New_Name
---------------
A B
B C
C D
G H
H I
Z B
I want to create a new table that links all the names that are related to a single new groupID
GroupID Name
-------------
1 A
1 B
1 C
1 D
1 Z
2 G
2 H
2 I
I'm a bit stuck on this can can't figure out how to do it apart from a bunch of joins. But I would like to do it properly.
Edited the question to allow grouping from two different start points, A and Z into one group.
Since you've changed the question, I'm updating the answer. Please note that answer is same in sense of logical structure. All it does differently is that instead of going from G to I in calculating levels, answer now calculates from I to G.
Working demo link
;with cte as
(
select
t1.Name as Name, row_number() over (order by t1.Name) r,
t1.New_Name as New_Name,
1 as level
from yt t1 left join yt t2
on t1.New_Name=t2.name
where t2.name is null
union all
select
yt.Name as Name, r,
yt.New_Name as New_Name,
c.level+1 as level
from cte c join yt
on yt.New_Name=c.Name
),
cte2 as
(
select r as group_id, Name from cte
union
select c1.r as group_id, c1.New_name as Name from cte c1
where level = (select min(level) from cte c2 where c2.r=c1.r)
)
select * from cte2;
Below is old answer.
You can try below CTE based query:
create table yt (Name varchar(10), New_Name varchar(10));
insert into yt values
('A','B'),
('B','C'),
('C','D'),
('G','H'),
('H','I');
;with cte as
(
select
t1.Name as Name, row_number() over (order by t1.Name) r,
t1.New_Name as New_Name,
1 as level
from yt t1 left join yt t2
on t1.Name=t2.New_name
where t2.new_name is null
union all
select
yt.Name as Name, r,
yt.New_Name as New_Name,
c.level+1 as level
from cte c join yt
on yt.Name=c.New_Name
),
cte2 as
(
select r as group_id, Name from cte
union
select c1.r as group_id, c1.New_name as Name from cte c1
where level = (select max(level) from cte c2 where c2.r=c1.r)
)
select * from cte2;
see working demo
Little bit complex but working.
DECLARE #T TABLE (Name VARCHAR(2), New_Name VARCHAR(2))
INSERT INTO #T
VALUES
('A','B'),
('B','C'),
('C','D'),
('G','H'),
('H','I'),
('Z','B')
;WITH CTE AS
(
SELECT * , RN = ROW_NUMBER() OVER(ORDER BY Name) FROM #T
)
,CTE2 AS (SELECT T1.RN, T1.Name Name1, T1.New_Name New_Name1,
X.Name Name2, X.New_Name New_Name2,
FLAG = CASE WHEN X.Name IS NULL THEN 1 ELSE 0 END
FROM CTE T1
OUTER APPLY (SELECT * FROM CTE T2 WHERE T2.RN > T1.RN
AND (T2.Name IN (T1.Name , T1.New_Name)
OR T2.New_Name IN (T1.Name , T1.New_Name)
)) AS X
)
,CTE3 AS (SELECT *,
GroupID = ROW_NUMBER() OVER (ORDER BY RN) -
ROW_NUMBER() OVER (PARTITION BY Flag ORDER BY RN) +1
FROM CTE2
)
SELECT
DISTINCT GroupID, Name
FROM
(SELECT * FROM CTE3 WHERE Name2 IS NOT NULL) SRC
UNPIVOT ( Name FOR COL IN ([Name1], [New_Name1], [Name2], [New_Name2])) UNPVT
Result
GroupID Name
-------------------- ----
1 A
1 B
1 C
1 D
1 Z
2 G
2 H
2 I

Select unique field

I have this table:
TableA
----------------
ID (pk) Name
1 A
2 B
3 C
4 A
5 D
6 A
7 B
8 A
9 D
10 C
....
I need to randomly extract with a SELECT TOP 5 ID, Name FROM TableA
with Name that must be unique within the 5 records.
I'm trying :
;WITH group
AS
(
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY NewId()) rn
FROM TableA
)
SELECT ID, Name
FROM group
WHERE rn = 1
but every time I have quite the same results.
I need to select between all the values for ID at random, assuring that Name will always be different for each record.
I hope the problem is understandable. Any ideas?
Found a solution. It seems to work!
;WITH group
AS (
SELECT ID, Name, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY NewId()) rn FROM TableA )
SELECT top 5 ID, Name, NewId() [NewId]
FROM group
WHERE rn = 1
ORDER BY [newid]
Perhaps the problem is that although newid() is random, it may tend to be sequential. Does this fix the problem?
WITH g as (
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY RAND(CHECKSUM(NewId()))) as rn
FROM TableA
)
SELECT ID, Name
FROM g
WHERE rn = 1;
CREATE TABLE #test(ID INT ,Name VARCHAR(1)) INSERT INTO #test(ID ,Name )
SELECT 1,'A' UNION ALL SELECT 2,'B' UNION ALL SELECT 3,'C' UNION ALL
SELECT 4,'A' UNION ALL SELECT 5,'D'UNION ALL SELECT 6,'A' UNION ALL
SELECT 7,'B' UNION ALL SELECT 8,'A'UNION ALL SELECT 9,'D' UNION ALL
SELECT 10,'C'
SELECT T1.ID ,T1.Name FROM #test T1
JOIN ( SELECT TOP 5 Name FROM #test T2 ORDER BY NEWID()
) A ON T1.Name = A.Name ORDER BY A.Name
;WITH group
AS
(
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY NewId()) rn
FROM TableA
)
SELECT top 5 ID, Name, NewId() [NewId]
FROM group
WHERE rn = 1
ORDER BY [newid]

concatenate recursive cross join

I need to concatenate the name in a recursive cross join way. I don't know how to do this, I have tried a CTE using WITH RECURSIVE but no success.
I have a table like this:
group_id | name
---------------
13 | A
13 | B
19 | C
19 | D
31 | E
31 | F
31 | G
Desired output:
combinations
------------
ACE
ACF
ACG
ADE
ADF
ADG
BCE
BCF
BCG
BDE
BDF
BDG
Of course, the results should multiply if I add a 4th (or more) group.
Native Postgresql Syntax:
SqlFiddleDemo
WITH RECURSIVE cte1 AS
(
SELECT *, DENSE_RANK() OVER (ORDER BY group_id) AS rn
FROM mytable
),cte2 AS
(
SELECT
CAST(name AS VARCHAR(4000)) AS name,
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
CAST(CONCAT(c2.name,c1.name) AS VARCHAR(4000)) AS name
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT name as combinations
FROM cte2
WHERE LENGTH(name) = (SELECT MAX(rn) FROM cte1)
ORDER BY name;
Before:
I hope if you don't mind that I use SQL Server Syntax:
Sample:
CREATE TABLE #mytable(
ID INTEGER NOT NULL
,TYPE VARCHAR(MAX) NOT NULL
);
INSERT INTO #mytable(ID,TYPE) VALUES (13,'A');
INSERT INTO #mytable(ID,TYPE) VALUES (13,'B');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'C');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'D');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'E');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'F');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'G');
Main query:
WITH cte1 AS
(
SELECT *, rn = DENSE_RANK() OVER (ORDER BY ID)
FROM #mytable
),cte2 AS
(
SELECT
TYPE = CAST(TYPE AS VARCHAR(MAX)),
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
[Type] = CAST(CONCAT(c2.TYPE,c1.TYPE) AS VARCHAR(MAX))
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT *
FROM cte2
WHERE LEN(Type) = (SELECT MAX(rn) FROM cte1)
ORDER BY Type;
LiveDemo
I've assumed that the order of "cross join" is dependent on ascending ID.
cte1 generate DENSE_RANK() because your IDs contain gaps
cte2 recursive part with CONCAT
main query just filter out required length and sort string
The recursive query is a bit simpler in Postgres:
WITH RECURSIVE t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name
FROM tbl
)
, cte AS (
SELECT grp, name
FROM t
WHERE grp = 1
UNION ALL
SELECT t.grp, c.name || t.name
FROM cte c
JOIN t ON t.grp = c.grp + 1
)
SELECT name AS combi
FROM cte
WHERE grp = (SELECT max(grp) FROM t)
ORDER BY 1;
The basic logic is the same as in the SQL Server version provided by #lad2025, I added a couple of minor improvements.
Or you can use a simple version if your maximum number of groups is not too big (can't be very big, really, since the result set grows exponentially). For a maximum of 5 groups:
WITH t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name AS n
FROM tbl
)
SELECT concat(t1.n, t2.n, t3.n, t4.n, t5.n) AS combi
FROM (SELECT n FROM t WHERE grp = 1) t1
LEFT JOIN (SELECT n FROM t WHERE grp = 2) t2 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 3) t3 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 4) t4 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 5) t5 ON true
ORDER BY 1;
Probably faster for few groups. LEFT JOIN .. ON true makes this work even if higher levels are missing. concat() ignores NULL values. Test with EXPLAIN ANALYZE to be sure.
SQL Fiddle showing both.

How to select parents and children rows in SQL Server

I want to select all itens related to user_id 53 (parents and children) from the following table. It should be: 1, 2, 4, 8, 9.
my_table
--------------------------------------------
id parent_id user_id sequence depth
--------------------------------------------
1 null 50 1 1
2 1 52 1.2 2
3 1 52 1.3 2
4 2 53 1.2.4 3
5 2 52 1.2.5 3
6 3 52 1.3.6 3
7 3 51 1.3.7 3
8 4 51 1.2.4.8 4
9 4 51 1.2.4.9 4
With CTE I could select all children or parents, but I could'nt select children and parents with just one query. Below is the cte I'm using to select children.
Item and children
with cte as (
select t.id, t.parent_id, t.user_id
from my_table t
where t.user_id=53
union all
select t.id, t.parent_id, t.user_id
from my_table t
inner join cte c on (c.parent_id=t.id)
)
select t.* from cte t;
Item and parents
with cte as (
select t.id, t.parent_id, t.user_id
from my_table t
where t.user_id=53
union all
select t.id, t.parent_id, t.user_id
from my_table t
inner join cte c on (c.id=t.parent_id)
)
select t.* from cte t;
Thanks.
It is very convenient that you have the sequences. The parents have a sequence matching an initial subset of the one you are looking for. The same is true for the children, but in reverse.
The following comes close to what you want:
select mt.*
from (select sequence from my_table where USER_ID = 53) theone join
my_table mt
on mt.sequence like theone.sequence+ '%' or
theone.sequence like mt.sequence + '%'
However, you have to be careful with 10 matching 1, for instance. So, let's add an additional period where appropriate:
select mt.*
from (select sequence from my_table where USER_ID = 53) theone join
my_table mt
on mt.sequence like theone.sequence+ '.%' or
theone.sequence like mt.sequence + '.%'
The problem here is, that you want a recursive SELECT. A DBMS is not made for such a query. But it's of course possible.
SELECT t1.* FROM Table1 t1 WHERE user_id = 53
UNION ALL
SELECT t2.* FROM Table1 t1, Table1 t2
WHERE
(t1.id = t2.parent_id OR t1.parent_id = t2.id) AND t1.user_id = 53
This query would give you each 1st degree relative.
For a recursive SELECT, have a look here: Recursive select in SQL
Solution for your query here: http://sqlfiddle.com/#!6/e1542/10/0
with cte as (
select t.id, t.parent_id, t.user_id
from Table1 t
where t.user_id=53
),
cte1 as (
select t.id, t.parent_id, t.user_id
from cte t
union all
select t.id, t.parent_id, t.user_id
from Table1 t
inner join cte1 c on (c.parent_id=t.id)
),
cte2 as (
select t.id, t.parent_id, t.user_id
from cte t
union all
select t.id, t.parent_id, t.user_id
from Table1 t
inner join cte2 c on (c.id=t.parent_id)
)
select t.* from cte1 t
UNION
select t.* from cte2 t
declare #Rownumber int;
CREATE TABLE #GetSeqUser (
RowNumber int,
UserId int,
)
INSERT INTO #GetSeqUser Select ROW_NUMBER() OVER(ORDER BY EmpId ASC) AS ROWNUMBER, EmpId FROM dbo.TreeSystem
set #Rownumber = (select ROWNUMBER from #GetSeqUser where UserId = 3 )
set #Rownumber = #Rownumber + 1
select UserId from #GetSeqUser where ROWNUMBER = #Rownumber
declare #Rownumber int;
CREATE TABLE #GetSeqUser (
RowNumber int,
UserId int,
)
INSERT INTO #GetSeqUser Select ROW_NUMBER() OVER(ORDER BY EmpId ASC) AS ROWNUMBER, EmpId FROM dbo.TreeSystem
set #Rownumber = (select ROWNUMBER from #GetSeqUser where UserId = 3 )
set #Rownumber = #Rownumber + 1
select UserId from #GetSeqUser where ROWNUMBER = #Rownumber

How to find level of employee position using RECURSIVE COMMON TABLE EXPRESSION

;with Ranked(Empid,Mngrid,Empnm,RN,level) As
(select Empid,Mngrid ,Empnm ,row_number() over (order by Empid)AS RN ,
0 as level from dbo.EmpMngr),
AnchorRanked(Empid,Mngrid,Empnm,RN,level)
AS(select Empid,Mngrid,Empnm,RN ,level from Ranked ),
RecurRanked(Empid,Mngrid,Empnm,RN,level)
AS(select Empid,Mngrid,Empnm,RN,level from AnchorRanked
Union All
select Ranked.Empid,Ranked.Mngrid,Ranked.Empnm,Ranked.RN,Ranked.level + 1
from Ranked
inner join RecurRanked
on Ranked.Empid = RecurRanked.Empid AND
Ranked.RN = RecurRanked.RN+1)
select Empid,Empnm,level from RecurRanked
This may help CTE Example of a simple hierarchy
;WITH OrgChart AS
(
SELECT EmpID, EmpNm, [Level] = 0
FROM EmpMngr
WHERE MngrID IS NULL
UNION ALL
SELECT e.EmpID, e.EmpNm, [Level] + 1
FROM OrgChart o
JOIN EmpMngr e ON (e.MngrID = o.EmpID)
)
SELECT EmpID, EmpNm, [Level] FROM OrgChart