Counting number of children in hierarchical SQL data - sql

for a simple data structure such as so:
ID parentID Text Price
1 Root
2 1 Flowers
3 1 Electro
4 2 Rose 10
5 2 Violet 5
6 4 Red Rose 12
7 3 Television 100
8 3 Radio 70
9 8 Webradio 90
For reference, the hierarchy tree looks like this:
ID Text Price
1 Root
|2 Flowers
|-4 Rose 10
| |-6 Red Rose 12
|-5 Violet 5
|3 Electro
|-7 Television 100
|-8 Radio 70
|-9 Webradio 90
I'd like to count the number of children per level. So I would get a new column "NoOfChildren" like so:
ID parentID Text Price NoOfChildren
1 Root 8
2 1 Flowers 3
3 1 Electro 3
4 2 Rose 10 1
5 2 Violet 5 0
6 4 Red Rose 12 0
7 3 Television 100 0
8 3 Radio 70 1
9 8 Webradio 90 0
I read a few things about hierarchical data, but I somehow get stuck on the multiple inner joins on the parentIDs. Maybe someone could help me out here.

Using a CTE would get you what you want.
Recursively go through all children, remembering the root.
COUNT the items for each root.
JOIN these again with your original table to produce the results.
Test Data
DECLARE #Data TABLE (
ID INTEGER PRIMARY KEY
, ParentID INTEGER
, Text VARCHAR(32)
, Price INTEGER
)
INSERT INTO #Data
SELECT 1, Null, 'Root', NULL
UNION ALL SELECT 2, 1, 'Flowers', NULL
UNION ALL SELECT 3, 1, 'Electro', NULL
UNION ALL SELECT 4, 2, 'Rose', 10
UNION ALL SELECT 5, 2, 'Violet', 5
UNION ALL SELECT 6, 4, 'Red Rose', 12
UNION ALL SELECT 7, 3, 'Television', 100
UNION ALL SELECT 8, 3, 'Radio', 70
UNION ALL SELECT 9, 8, 'Webradio', 90
SQL Statement
;WITH ChildrenCTE AS (
SELECT RootID = ID, ID
FROM #Data
UNION ALL
SELECT cte.RootID, d.ID
FROM ChildrenCTE cte
INNER JOIN #Data d ON d.ParentID = cte.ID
)
SELECT d.ID, d.ParentID, d.Text, d.Price, cnt.Children
FROM #Data d
INNER JOIN (
SELECT ID = RootID, Children = COUNT(*) - 1
FROM ChildrenCTE
GROUP BY RootID
) cnt ON cnt.ID = d.ID

Consider using a modified preorder tree traversal way of storing the hierarchical data. See http://www.sitepoint.com/hierarchical-data-database/
Determining number of children for any node then becomes a simple:
SELECT (right-left-1) / 2 AS num_children FROM ...

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.

oracle sql parent child with for multiple columns

I have a users table having two columns for Approval Hierarchy , Table structure is like below
User_ID Submit_to Approve_to
1 2 3
2 4 5
3 6 2
4 2 3
5 1 0
Data is just For example 0 Mention no Approver :
Submit to and Approve to both will be Approvers
I need a Query which can give the Approves details in sequence that who will be next approver for entry user have created .
Using CONNECT BY, you can connect the parent/child relationship within a table. The query below will show the approval path for each user.
Query
WITH
d (user_id, submit_to, approve_to)
AS
(SELECT 1, 2, 3 FROM DUAL
UNION ALL
SELECT 2, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 6, 2 FROM DUAL
UNION ALL
SELECT 4, 2, 3 FROM DUAL
UNION ALL
SELECT 5, 1, 0 FROM DUAL)
SELECT d.user_id, LTRIM (SYS_CONNECT_BY_PATH (user_id, '<'), '<') AS approval_path
FROM d
START WITH approve_to = 0
CONNECT BY NOCYCLE PRIOR user_id = approve_to
ORDER BY user_id;
Result
In the APPROVAL_PATH column, the left most number is the USER_ID giving final approval and the right most number is the USER_ID initially submitting whatever needs to be approved.
USER_ID | APPROVAL_PATH
-----------------------
1 | 5<2<3<1
2 | 5<2
3 | 5<2<3
4 | 5<2<3<4
5 | 5

Select Query on Sql Server

The question may be very simple but i don't know how to fix it,
I have this table structure
sno left Right
1 2 1
2 2 2
3 1 2
4 3 1
5 2 4
6 7 1
7 2 8
How do I get a result set like the one below
sno left Right Result
1 2 1 1
2 2 2 2
3 1 2 1
4 3 1 1
5 2 4 2
6 7 1 1
7 2 8 2
I wanna select the Data what mimimum value is matched between two columns,
Eg:3 and 1
1 is minimum value between these two and 1 is matched with 3, so the matched value is 1.
eg: 2 and 4
2 is minimum value between these two and 2 is is mathed with 4, so the matched value is 2.
Edited:
If choose 8 and 2 for example
8 contains(1,2,3,4,5,6,7,8)
2 contains(1,2)
So the Result is 2
Because 2 values are matched here.
I hope i explained it well, thanks
The following SQL will return the positive value of a subtraction operation between the left and right values - in a column with Result as the header. It will calculate the difference between left and right values - ABS will make the result positive.
SELECT
sno,
left,
Right,
ABS(left - right) AS Result
FROM tablename
One of the possible solutions:
DECLARE #t TABLE ( sno INT, l INT, r INT )
INSERT INTO #t
VALUES ( 1, 2, 1 ),
( 2, 2, 2 ),
( 3, 1, 2 ),
( 4, 3, 1 ),
( 5, 2, 4 ),
( 6, 7, 1 ),
( 7, 2, 8 )
SELECT *,
(SELECT MIN(v) FROM (VALUES(l),(r)) m(v)) AS m
FROM #t
Output:
sno l r m
1 2 1 1
2 2 2 2
3 1 2 1
4 3 1 1
5 2 4 2
6 7 1 1
7 2 8 2
case
when left < right then left
else right
end

Parent child hierarchy in sql server

I have the following requirement,
Input
CID ParentID
1 10
2 1
3 1
4 2
5 2
6 2
7 2
8 4
9 4
10 NULL
20 25
30 38
15 51
17 71
when I pass child value as 4 following is my desired output:
Desired Output:
CID ParentID
2 4
1 2
10 1
NULL 10
4 2
4 8
2 4
2 5
2 6
1 3
2 7
    
Please help! Thanks in advance.
Your explanation and your data don't match. I think the row (4, 2) in the results should be (4,9). If I'm correct, then this should do what you want:
with Parents(lvl, CID, ParentID) as (
select 0, ParentID, CID
from T
where CID = 4
union all
select lvl+1, T.ParentID, T.CID
from T join Parents
on T.CID = Parents.CID
)
select CID, ParentID
from Parents
union all
select T.ParentID, T.CID
from T join Parents
on Parents.ParentID = T.ParentID;
SQL fiddle here.

Query to serialize data

I have two tables:
Routes
ID Description
1 street1
2 street2
3 street3
4 street4
5 street5
Segments
ID RouteID, Progres, LabelStart, LabelEnd
1 1 5 1 A 21 B
2 1 10 2 A 10
3 2 15 3 25
4 2 15 2 20
5 3 20 1 11
6 3 22 4 10
7 4 30 5 11
8 4 31 2 12
I need a sequence with these rules:
table must be ordered by Progress ASC
A column Type is defined and take O if LabelStart and LabelEnd are Odd, E if Even
if two routes have the same progress then the rows are merged in one where
LabelStart is the minimum (among LabelStart Odd and LabelStart Even)
and LabelEnd is the Max, in this case Type takes the value of A (All)
as per example data above the result should be
Sequence
ID RouteID, Progres, LabelStart, LabelEnd Type
1 1 5 1 A 21 B O
2 1 10 2 A 10 E
4 2 15 2 25 A
5 3 20 1 11 O
6 3 22 4 10 E
7 4 30 5 11 O
8 4 31 2 12 E
It is for Postgres 9.2
This was an interesting query to write because you had letters in your LabelStart and LabelEnd fields. I used REGEX_REPLACE to remove those. Then I used a CTE to get the records that had more than one routeid and progress rows.
I think this should do it:
WITH CTE AS (
SELECT
RouteId, Progress
FROM Sequence
GROUP BY RouteId, Progress
HAVING COUNT(DISTINCT Id) > 1
)
SELECT MAX(S.ID) Id,
T.RouteId,
T.Progress,
MIN(regexp_replace(LabelStart, '[^0-9]', '', 'g')) LabelStart,
MAX(regexp_replace(LabelStart, '[^0-9]', '', 'g')) LabelEnd,
'A' as Type
FROM Sequence S
INNER JOIN CTE T ON S.RouteId = T.RouteId AND S.Progress = T.Progress
GROUP BY T.RouteId, T.Progress
UNION
SELECT S.Id,
S.RouteId,
S.Progress,
S.LabelStart,
S.LabelEnd,
CASE
WHEN CAST(regexp_replace(LabelStart, '[^0-9]', '', 'g') as int) % 2 = 0
THEN 'E'
ELSE 'O'
END
FROM Sequence S
LEFT JOIN CTE T ON S.RouteId = T.RouteId AND S.Progress = T.Progress
WHERE T.RouteId IS NULL
ORDER BY Progress ASC
And some sample Fiddle.