Create a tree structure from a structured table in sql database - sql

I have a table that tracks movement of data, it looks like:
From_Id
To_Id
NULL
1
1
2
NULL
3
3
4
4
5
4
6
5
7
6
10
2
8
8
9
5
9
9
NULL
10
NULL
I want to structure and store every possible paths (e.g 1,2,8,9) and im unsure about the best possible way to do this with SQL.
I started off with a simple while loop, but got a problem at id=4 where it gets 2 new paths. I tried finding a solution which uses a tree structure, but I couldnt find anything that fits my case.
What is a good solution here?
Edit:
Im using microsoft sql server
desired output:
From_Id
To_Id
path
NULL
1
1,
1
2
1,2,
NULL
3
3,
3
4
3,4,
4
5
3,4,5,
4
6
3,4,6,
5
7
3,4,5,7,
6
10
3,4,6,10,
2
8
1,2,8,
8
9
1,2,8,9,
5
9
3,4,5,9,
9
NULL
NULL
10
NULL
NULL
I tried this from How to call a recursive function in sql server
DECLARE #TABLE2 TABLE(
From_Id INT,
To_id INT
)
INSERT INTO #TABLE2 SELECT NULL,1
INSERT INTO #TABLE2 SELECT 1,2
INSERT INTO #TABLE2 SELECT NULL,3
INSERT INTO #TABLE2 SELECT 3,4
INSERT INTO #TABLE2 SELECT 4,5
INSERT INTO #TABLE2 SELECT 4,6
INSERT INTO #TABLE2 SELECT 5,7
INSERT INTO #TABLE2 SELECT 6,10
INSERT INTO #TABLE2 SELECT 2,8
INSERT INTO #TABLE2 SELECT 8,9
INSERT INTO #TABLE2 SELECT 5,9
INSERT INTO #TABLE2 SELECT 9,NULL
INSERT INTO #TABLE2 SELECT 10,NULL
;WITH Recursives AS (
SELECT *,
CAST(To_id AS VARCHAR(MAX)) + ',' ID_Path
FROM #TABLE2
WHERE From_Id IS NULL
UNION ALL
SELECT t.*,
r.ID_Path + CAST(t.To_id AS VARCHAR(MAX)) + ','
FROM #TABLE2 t INNER JOIN
Recursives r ON t.From_Id = r.To_id
)
SELECT *
FROM Recursives
Which gave me an extra row (it gets much larger when i have 10k + rows)
From_Id
To_Id
ID_Path
NULL
1
1,
1
2
1,2,
NULL
3
3,
3
4
3,4,
4
5
3,4,5,
4
6
3,4,6,
5
7
3,4,5,7,
6
10
3,4,6,10
2
8
1,2,8,
8
9
1,2,8,9,
5
9
3,4,5,9,
9
NULL
NULL
9
NULL
NULL
10
NULL
NULL

Create a many-to-many relationship. A new table will be created that will only store id pairs if they exist. In the case of id 4, 2 separate records will be created. Of course, if you want to display records from a new array, you can also use the select command.
The problem arises if the From_Id and To_Id fields come from the same table. In this case, an intermediate table should be created. I will use a simple one here.
An example of a social networking site. We have a user tracking feature. In this way, we create 2 instances of the follower of the user and observe. We cannot create a direct mana-to-many relationship because this cannot directly refer to the same table. We create 2 additional tables. One will save users as followers and the other as followed. It is between these tables that we create a many-to-many relationship.

Related

Using recursive CTE to generate hierarchy results ordered by depth without the use of heiarchyid

I would like to query hierarchy results ordered by depth first without the use of SQL's heiarchyid built in function. Essentially, I am hoping to accomplish the depth ordering without any fancy functions.
I have provided a temp table below that contains these records:
Id
p_Id
order1
name1
1
null
1
josh
2
null
2
mary
3
null
3
george
4
1
1
joe
5
1
2
jeff
6
2
1
marg
7
2
2
moore
8
2
3
max
9
3
1
gal
10
3
2
guy
11
4
1
tod
12
4
2
ava
13
9
1
ron
14
9
2
bill
15
9
100
pat
where p_Id is the id of the parent record, and order1 is essentially just the ordering of which the depth first output should be displayed. To show why my query does not fully work, I made the order1 of the last record 100 instead of say, 3. However this should not ultimately matter since 100 and 3 both come after the previous order1 value, 2.
An example of a correct result table is shown below:
Id
p_Id
order1
name1
Descendants
1
null
1
josh
josh
4
1
1
joe
josh/joe
11
4
1
tod
josh/joe/tod
12
4
2
ava
josh/joe/ava
5
1
2
jeff
josh/jeff
2
null
2
mary
mary
6
2
1
marg
mary/marg
7
2
2
moore
mary/moore
8
2
3
max
mary/max
3
null
3
george
george
9
3
1
gal
george/gal
13
9
1
ron
george/gal/ron
15
9
2
bill
george/gal/bill
14
9
100
pat
george/gal/pat
10
3
2
guy
george/guy
Where an example of my results are shown below:
Id
p_Id
order1
name1
Descendants
levels
1
null
1
josh
josh
.1
4
1
1
joe
josh/joe
.1.1
11
4
1
tod
josh/joe/tod
.1.1.1
12
4
2
ava
josh/joe/ava
.1.1.2
5
1
2
jeff
josh/jeff
.1.2
2
null
2
mary
mary
.2
6
2
1
marg
mary/marg
.2.1
7
2
2
moore
mary/moore
.2.2
8
2
3
max
mary/max
.2.3
3
null
3
george
george
.3
9
3
1
gal
george/gal
.3.1
13
9
1
ron
george/gal/ron
.3.1.1
15
9
100
pat
george/gal/pat
.3.1.100
14
9
2
bill
george/gal/bill
.3.1.2
10
3
2
guy
george/guy
.3.2
where I have created a levels column that essentially concatenates the order1 values and separates them with a period. This almost returns the correct results, but due to the fact that I am ordering by this string (of numbers and periods), the levels value of .3.1.100 will come before .3.1.2 , which is not what the desired output should look like. I am sure there is a different method to return the correct depth order. See below for the code that generates a temp table, and the code that I used to generate the incorrect output that I have so far.
if object_id('tempdb..#t1') is not null drop table #t1
CREATE TABLE #t1 (Id int, p_Id int, order1 int, name1 varchar(150))
INSERT into #t1 VALUES
(1, null, 1, 'josh'),
(2, null, 2, 'mary'),
(3, null, 3, 'george'),
(4, 1, 1, 'joe'),
(5, 1, 2, 'jeff'),
(6, 2, 1, 'marg'),
(7, 2, 2, 'moore'),
(8, 2, 3, 'max'),
(9, 3, 1, 'gal'),
(10, 3, 2, 'guy'),
(11, 4, 1, 'tod'),
(12, 4, 2, 'ava'),
(13, 9, 1, 'ron'),
(14, 9, 2, 'bill'),
(100, 9, 100, 'pat');
select * from #t1
-- Looking to generate heiarchy results ordered by depth --
; with structure as (
-- Non-recursive term.
-- Select the records where p_Id is null
select p.Id,
p.p_Id,
p.order1,
p.name1,
cast(p.name1 as varchar(64)) as Descendants,
cast(concat('.', p.order1) as varchar(150)) as levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term.
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)) as Descendants,
cast(concat(p.levels, '.', c.order1) as varchar(150)) as levels
from #t1 c -- c being the 'child' records
inner join structure p -- p being the 'parent' records
on c.p_Id = p.Id
)
select *
from structure
order by replace(levels, '.', '') asc
Take II. As pointed out by OP my original answer fails for more than 10 children. So what we can do (OP's suggestion) is pad the values out with zeros to a constant length. But what length? We need to take the largest number of children under a node and add this to the largest value or order, so for the example provided this is 100 + 3, and then take the length of that (3) and pad every order with zeros to 3 digits long. This means we will always be ordering as desired.
declare #PadLength int = 0;
select #PadLength = max(children)
from (
select len(convert(varchar(12),max(order1)+count(*))) children
from #t1
group by p_Id
) x;
-- Looking to generate heiarchy results ordered by depth --
with structure as (
-- Non-recursive term
-- Select the records where p_Id is null
select
p.Id [Id]
, p.p_Id [ParentId]
, p.order1 [OrderBy]
, p.name1 [Name]
, cast(p.name1 as varchar(64)) Descendants
, concat('.', right(replicate('0',#Padlength) + convert(varchar(12),p.order1), #PadLength)) Levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select
c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)),
concat(p.levels, '.', right(replicate('0',#Padlength) + convert(varchar(12),c.order1), #PadLength))
from #t1 c -- c being the 'child' records
inner join structure p on c.p_Id = p.Id -- p being the 'parent' records
)
select *
from structure
order by replace(levels, '.', '') asc;
Note: This answer fails in the case when there are more than 10 children under a particular node. Leaving for interest.
So this issue you have run into is that you are ordering by a string not a number. So the string 100 comes before the string 2. But you need to order by a string to take care of the hierarchy, so one solution is to replace order1 with row_number() based on the order1 column while its still a number and use the row_number() to build your ordering string.
So you replace:
cast(concat(p.levels, '.', c.order1) as varchar(150)) as levels
with
cast(concat(p.levels, '.', row_number() over (order by c.Order1)) as varchar(150))
giving a full query of
with structure as (
-- Non-recursive term.
-- Select the records where p_Id is null
select p.Id,
p.p_Id,
p.order1,
p.name1,
cast(p.name1 as varchar(64)) as Descendants,
cast(concat('.', p.order1) as varchar(150)) as levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term.
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)) as Descendants,
cast(concat(p.levels, '.', row_number() over (order by c.Order1)) as varchar(150))
from #t1 c -- c being the 'child' records
inner join structure p -- p being the 'parent' records
on c.p_Id = p.Id
)
select *
from structure
order by replace(levels, '.', '') asc;
Which returns the desired results.
Note: good question, well written.

DB2 : search depth first : (syntax) error

A few days ago I installed DB2 LUW (11.5) on a server to play around with.
Now I would like to do some recursive SQL (Recursive Common Table Expression):
Let me show how I setup :
drop table relations;
create table relations (id int, parent int);
insert into relations values(0,NULL);
insert into relations values(1,0);
insert into relations values(2,1);
insert into relations values(3,1);
insert into relations values(4,3);
insert into relations values(5,0);
insert into relations values(6,5);
insert into relations values(7,5);
insert into relations values(8,6);
insert into relations values(9,7);
insert into relations values(10,0);
insert into relations values(11,1);
commit;
Now I would like to see the hierarchy in the table. So I tried the following:
with recur(id, parent, level) as
(
select rel.id id, rel.parent parent, 0 level from relations rel where rel.id=0
union all
select rel.id, rel.parent, rec.level+1 from recur rec, relations rel where rec.id=rel.parent
and rec.level<10
)
select id, lpad(parent, level*2, ' ') from recur;
This gives me:
ID PARENT
----------- ------------------
0 -
1 0
5 0
10 0
2 1
3 1
11 1
6 5
7 5
4 3
8 6
9 7
This is (to me) : "Search Breadth First"
What I would like to see is "Search Depth First"
So I did this:
with recur(id, parent, level) as
(
select rel.id id, rel.parent parent, 0 level from relations rel where rel.id=0
union all
select rel.id, rel.parent, rec.level+1 from recur rec, relations rel where rec.id=rel.parent
and rec.level<10
)
search depth first by parent set ord
select id, lpad(parent, level*2, ' ') parent from recur order by ord;
But this delivers to me:
SQL0104N An unexpected token "search depth first by parent set ord sel" was
found following "t and rec.level<10 )". Expected tokens may include:
"<values>". SQLSTATE=42601
No clue how to solve it now. I (think I) have tried a lot of possible solutions. But none worked.
I'm starting to believe that DB2 LUW (11.5) doesn't know about Search Depth First. Or some setting must be made to make DB2 aware of the "SDF" possibility.
My question to you all:
How to solve this problem? How do I get Search Depth First to work?
On the positive....following works like a charms....but that is not whatI want tot know :-)
select id, lpad(parent, level*2, ' ') parent, level
from relations
start with id=0
connect by prior id=parent;
ID PARENT LEVEL
----------- ---------- -----------
0 - 1
1 0 2
2 1 3
3 1 3
4 3 4
11 1 3
5 0 2
6 5 3
8 6 4
7 5 3
9 7 4
10 0 2
This works like a charm, but I had tot make a switch in the database (and a restart):
db2set DB2_COMPATIBILITY_VECTOR=08
Your question is about displaying rows in a specific ordering, not about searching in a specific ordering.
You can display the rows in the ordering you want by assembling an ordering column that fits your needs.
For example:
with
n (id, parent, lvl, ordering) as (
select id, parent, 1, lpad(id, 3, '0') || lpad('', 30, ' ')
from relations
where parent is null
union all
select r.id, r.parent, n.lvl + 1, trim(n.ordering) || '/' || lpad(r.id, 3, '0')
from n, relations r where r.parent = n.id
)
select id, lpad(parent, lvl * 2, ' ') as parent, lvl
from n
order by ordering;
Result:
ID PARENT LVL
--- --------- ---
0 1
1 0 2
2 1 3
3 1 3
4 3 4
11 1 3
5 0 2
6 5 3
8 6 4
7 5 3
9 7 4
10 0 2
See running example at db<>fiddle.

SQL - Add value with previous row only

I have a table named myvals with the following fields:
ID number
-- -------
1 7
2 3
3 4
4 0
5 9
Starting on 2nd row, I would like to add the number with the previous row number. So, my end result would look like this
ID number
-- ------
1 7
2 10
3 7
4 4
5 9
You could use the LAG analytic function
SELECT Id, number + LAG(number,1,0) OVER (ORDER BY Id) FROM table
First thing's first. You can't add to null to ID 1 must have a value.
create table #temp
(
month_type datetime,
value int
)
insert into #temp
Select '2015/01/01',1
union
Select '2015/02/01',2
union
Select '2015/03/01',3
union
Select '2015/04/01',4
SELECT t.value,t1.value,(t.value+t1.value)/2 FROM #temp t1
left join #temp t on t.month_type=Dateadd(MONTH,-1,t1.month_type)

Sql Query to find reporting user details

i have a table with follow fields
UserID UserName ReportingUserID
1 Vinoth Null
2 Kumar 1
3 Raja 1
4 Ragu 2
5 Vignesh 2
6 Yoga 3
7 Yogesh 2
8 Eswar 4
9 Esakki 3
. ... .
. .... .
if i gave UserID as 1 then a query will display all users reporting to him will be displayed
if i gave USeRID as 1
Then result will be
UserID UserName ReportingUserID
2 Kumar 1
3 Raja 1
4 Ragu 2
5 Vignesh 2
6 Yoga 3
7 Yogesh 2
8 Eswar 4
9 Esakki 3
. ... .
. .... .
Have a look at using a recursive CTE.
A common table expression (CTE) can be thought of as a temporary
result set that is defined within the execution scope of a single
SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement. A CTE is
similar to a derived table in that it is not stored as an object and
lasts only for the duration of the query. Unlike a derived table, a
CTE can be self-referencing and can be referenced multiple times in
the same query.
Something like
DECLARE #Table TABLE(
UserID INT,
UserName VARCHAR(20),
ReportingUserID INT
)
INSERT INTO #Table VALUES
(1,'Vinoth',Null),
(2,'Kumar',1),
(3,'Raja',1),
(4,'Ragu',2),
(5,'Vignesh',2),
(6,'Yoga',3),
(7,'Yogesh',2),
(8,'Eswar',4),
(9,'Esakki',3)
DECLARE #UserID INT = 1
;WITH ReportingUsers AS (
SELECT *
FROM #Table
WHERE ReportingUserID = #UserID
UNION ALL
SELECT t.*
FROM #Table t INNER JOIN
ReportingUsers ru ON ru.UserID = t.ReportingUserID
)
SELECT *
FROM ReportingUsers

How to get hierarchy from multiple values on SQL Server?

I have table RD with only one column:
-----
rd_id
-----
3
2
6
7
8
I have table DL with two columns, there is hierarchy on stored on this table:
----------------------
dl_id dl_parent
----------------------
1 2
2 Null
3 Null
4 6
6 7
7 8
8 Null
Now the problem is how to get the hierarchy out from DL table using the member of RD table.
The result will be:
--------------
rd_id dl_id
--------------
3 3
2 2
6 6
6 7
6 8
7 7
8 8
8 8
I've been toiling with this problem from Friday and still can't get crack of it.
I know that I can use Common Table Expression to traverse the recursive from one value (like example create one function with input 6 and produce 6,7,8).
but I don't know how to use multiple value (rd_id).
Have some ideas?
This produces the correct results. Data setup:
declare #RD table (rd_id int not null)
insert into #RD(rd_id) values
(3),
(2),
(6),
(7),
(8)
declare #DL table (dl_id int not null,dl_parent int null)
insert into #DL(dl_id,dl_parent) values
(1,2),
(2,Null),
(3,Null),
(4,6),
(6,7),
(7,8),
(8,Null)
And the query:
;with AllValues as (
select rd_id,rd_id as dl_id from #RD
union all
select rd_id,dl_parent
from AllValues av
inner join
#DL dl
on
av.dl_id = dl.dl_id
where
dl.dl_parent is not null
)
select * from AllValues
Result:
rd_id dl_id
----------- -----------
3 3
2 2
6 6
7 7
8 8
7 8
6 7
6 8
Explanation:
In the anchor of the CTE, we simply select rd_id from the #RD table twice - since your sample implies that every input row should produce an output row with the same value in both columns.
We then join to the #DL table for any matching parent rows we can find, based on the second column. If we find a parent, then we produce a new row, substituting the parent value into the second column. This continues until no new rows are produced.
You have to use on-the-fly views to create a recursive query like this:
with n as (
select dl_id, dl_id as ancestor
from dbo.dl
union all
select np1.dl_id, n.ancestor
from dl as np1 , n
where n.dl_id = np1.dl_parent)
select * from n
where dl_id in (select rd_id from rd)
order by dl_id, ancestor