Find all members in a tree structure - sql

I have inherited a tree type table in this format
StatementAreaId | ParentStatementAreaId | SubjectId | Description
-----------------------------------------------------------------
1 | 0 | 100 | Reading
2 | 0 | 110 | Maths
3 | 2 | 0 | Number
4 | 2 | 0 | Shape
5 | 3 | 0 | Addition
6 | 3 | 0 | Subtraction
I want to find all the StatementAreaIds where the ultimate parent subject is, say maths (i.e. SubjectId=110). For instance if the SubjectId was Maths I'd get a list of StatementAreaIds in the tree:
StatementAreaId
---------------
2
3
4
5
6
The tree has a maximum of a depth of 3 if that helps.
Thanks

Recursive CTE to the rescue:
Create and populate sample table: (Please save us this step in your future questions)
DECLARE #T AS TABLE
(
StatementAreaId int,
ParentStatementAreaId int,
SubjectId int,
Description varchar(20)
)
INSERT INTO #T VALUES
(1 , 0 , 100 , 'Reading'),
(2 , 0 , 110 , 'Maths'),
(3 , 2 , 0 , 'Number'),
(4 , 2 , 0 , 'Shape'),
(5 , 3 , 0 , 'Addition'),
(6 , 3 , 0 , 'Subtraction')
Query:
;WITH CTE AS
(
SELECT StatementAreaId, ParentStatementAreaId
FROM #T
WHERE SubjectId = 110
UNION ALL
SELECT t1.StatementAreaId, t1.ParentStatementAreaId
FROM #T t1
INNER JOIN CTE ON t1.ParentStatementAreaId = CTE.StatementAreaId
)
SELECT StatementAreaId
FROM CTE
Results:
StatementAreaId
2
3
4
5
6

Related

how to rank row for same repeating value in sql

Given this data how can give rank for each repeating data. 1 to 5 i want to rank as 1 and next 1 to 5 i want to rank as 2
Data
1
2
3
4
5
1
2
3
4
5
Expecting output
Data | Column
1 1
2 1
3 1
4 1
5 1
1 2
2 2
3 2
4 2
5 2
I was trying to implement using row number but Below is the exact requirement that i have to implement :
Refcol value column
1 refers to time
2 refers to name
3 refers to location
4 refers to Available (1 or 0 or null)
ID | Refcol | Metric
1 1 02/02/2022
1 2 Adam
1 3 Japan
1 4 1
1 1 03/02/2022
1 2 Smith
1 3 England
1 4 0
Now i want to transform above data as shown below
Expected Ouput
ID | time | name | location | Available
1 02/02/2022 Adam Japan 1
1 03/02/2022 Smith England 0
Best you can do with the limited sample data is to create a row number for each time a number appears. Then order by the row number and then the number. If this doesn't work, then show us more real data.
When using ROW_NUMBER, there's no guarantee that the first 1 through 5 group will be ordered correctly. You have to have some other identifier to guarantee the ordering (i.e. time stamp, set number, parent group, etc.).
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE Numbers (
num int not null
);
INSERT INTO Numbers
VALUES (1),(2),(3),(4),(5),(1),(2),(3),(4),(5)
Query 1:
WITH prelim AS (
SELECT n.num
, ROW_NUMBER() OVER(PARTITION BY n.num ORDER BY n.num ASC) as row_num
FROM Numbers as n
)
SELECT
p.num
, p.row_num
FROM prelim as p
ORDER BY p.row_num, p.num
Results:
| num | row_num |
|-----|---------|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
| 5 | 2 |
UPDATE: If you are dead-set on not changing the data structure, then the best you can do is loop through the data and assigning a unique SetID number to each group of 4 rows. There is no guarantee that this will work when you have more than 4 rows in the table since you have no column to guarantee a consistent sort order.
CREATE TABLE attributes (
ID int not null
, RefCol int not null
, Metric nvarchar(50) not null
, SetID int null
);
INSERT INTO attributes (ID, RefCol, Metric)
VALUES
(1,1,'02/02/2022')
,(1,2,'Adam')
,(1,3,'Japan')
,(1,4,'1')
,(1,1,'03/02/2022')
,(1,2,'Smith')
,(1,3,'England')
,(1,4,'0')
;
DECLARE #setID int = 0;
WHILE (EXISTS (SELECT ID FROM attributes WHERE SetID is NULL))
BEGIN
UPDATE TOP (4) attributes
SET SetID = #setID
FROM attributes
WHERE SetID IS NULL
;
SET #setID = #setID + 1;
END
SELECT * FROM attributes;
SELECT DISTINCT
a.SetID
, a.ID
, aTime.Metric as [time]
, aName.Metric as [name]
, aLoc.Metric as [location]
, aAvail.Metric as [Available]
FROM attributes as a
LEFT OUTER JOIN attributes as aTime
ON aTime.SetID = a.SetID
AND aTime.RefCol = 1
LEFT OUTER JOIN attributes as aName
ON aName.SetID = a.SetID
AND aName.RefCol = 2
LEFT OUTER JOIN attributes as aLoc
ON aLoc.SetID = a.SetID
AND aLoc.RefCol = 3
LEFT OUTER JOIN attributes as aAvail
ON aAvail.SetID = a.SetID
AND aAvail.RefCol = 4
;
ID
RefCol
Metric
SetID
1
1
02/02/2022
0
1
2
Adam
0
1
3
Japan
0
1
4
1
0
1
1
03/02/2022
1
1
2
Smith
1
1
3
England
1
1
4
0
1
SetID
ID
time
name
location
Available
0
1
02/02/2022
Adam
Japan
1
1
1
03/02/2022
Smith
England
0
fiddle
You're probably better off asking the question you actually have.
The result you want can be achieved from the data you gave, but it's going to be non-deterministic.
DECLARE #ints TABLE (INT INT)
INSERT INTO #ints (INT) VALUES
(1), (2), (3), (4), (5),
(1), (2), (3), (4), (5)
SELECT INT, ROW_NUMBER() OVER (PARTITION BY INT ORDER BY INT) AS rn
FROM #ints
ORDER BY rn, INT
INT rn
------
1 1
2 1
3 1
4 1
5 1
1 2
2 2
3 2
4 2
5 2

How to count all the connected nodes (rows) in a graph on Postgres?

My table has account_id and device_id. One account_id could have multiple device_ids and vice versa. I am trying to count the depth of each connected many-to-many relationship.
Ex:
account_id | device_id
1 | 10
1 | 11
1 | 12
2 | 10
3 | 11
3 | 13
3 | 14
4 | 15
5 | 15
6 | 16
How do I construct a query that knows to combine accounts 1-3 together, 4-5 together, and leave 6 by itself? All 7 entries of accounts 1-3 should be grouped together because they all touched the same account_id or device_id at some point. I am trying to group them together and output the count.
Account 1 was used on device's 10, 11, 12. Those devices used other accounts too so we want to include them in the group. They used additional accounts 2 and 3. But account 3 was further used by 2 more devices so we will include them as well. The expansion of the group brings in any other account or device that also "touched" an account or device already in the group.
A diagram is shown below:
You can use a recursive cte:
with recursive t(account_id, device_id) as (
select 1, 10 union all
select 1, 11 union all
select 1, 12 union all
select 2, 10 union all
select 3, 11 union all
select 3, 13 union all
select 3, 14 union all
select 4, 15 union all
select 5, 15 union all
select 6, 16
),
a as (
select distinct t.account_id as a, t2.account_id as a2
from t join
t t2
on t2.device_id = t.device_id and t.account_id >= t2.account_id
),
cte as (
select a.a, a.a2 as mina
from a
union all
select a.a, cte.a
from cte join
a
on a.a2 = cte.a and a.a > cte.a
)
select grp, array_agg(a)
from (select a, min(mina) as grp
from cte
group by a
) a
group by grp;
Here is a SQL Fiddle.
You can GROUP BY the device_id and then aggregate together the account_id into a Postgres array. Here is an example query, although I'm not sure what your actual table name is.
SELECT
device_id,
array_agg(account_id) as account_ids
FROM account_device --or whatever your table name is
GROUP BY device_id;
Results - hope it's what you're looking for:
16 | {6}
15 | {4,5}
13 | {3}
11 | {1,3}
14 | {3}
12 | {1}
10 | {1,2}
-- \i tmp.sql
CREATE TABLE graph(
account_id integer NOT NULL --references accounts(id)
, device_id integer not NULL --references(devices(id)
,PRIMARY KEY(account_id, device_id)
);
INSERT INTO graph (account_id, device_id)VALUES
(1,10) ,(1,11) ,(1,12)
,(2,10)
,(3,11) ,(3,13) ,(3,14)
,(4,15)
,(5,15)
,(6,16)
;
-- SELECT* FROM graph ;
-- Find the (3) group leaders
WITH seed AS (
SELECT row_number() OVER () AS cluster_id -- give them a number
, g.account_id
, g.device_id
FROM graph g
WHERE NOT EXISTS (SELECT*
FROM graph nx
WHERE (nx.account_id = g.account_id OR nx.device_id = g.device_id)
AND nx.ctid < g.ctid
)
)
-- SELECT * FROM seed;
;
WITH recursive omg AS (
--use the above CTE in a sub-CTE
WITH seed AS (
SELECT row_number()OVER () AS cluster_id
, g.account_id
, g.device_id
, g.ctid AS wtf --we need an (ordered!) canonical id for the tuples
-- ,just to identify and exclude them
FROM graph g
WHERE NOT EXISTS (SELECT*
FROM graph nx
WHERE (nx.account_id = g.account_id OR nx.device_id = g.device_id) AND nx.ctid < g.ctid
)
)
SELECT s.cluster_id
, s.account_id
, s.device_id
, s.wtf
FROM seed s
UNION ALL
SELECT o.cluster_id
, g.account_id
, g.device_id
, g.ctid AS wtf
FROM omg o
JOIN graph g ON (g.account_id = o.account_id OR g.device_id = o.device_id)
-- AND (g.account_id > o.account_id OR g.device_id > o.device_id)
AND g.ctid > o.wtf
-- we still need to exclude duplicates here
-- (which could occur if there are cycles in the graph)
-- , this could be done using an array
)
SELECT *
FROM omg
ORDER BY cluster_id, account_id,device_id
;
Results:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 10
cluster_id | account_id | device_id
------------+------------+-----------
1 | 1 | 10
2 | 4 | 15
3 | 6 | 16
(3 rows)
cluster_id | account_id | device_id | wtf
------------+------------+-----------+--------
1 | 1 | 10 | (0,1)
1 | 1 | 11 | (0,2)
1 | 1 | 12 | (0,3)
1 | 1 | 12 | (0,3)
1 | 2 | 10 | (0,4)
1 | 3 | 11 | (0,5)
1 | 3 | 13 | (0,6)
1 | 3 | 14 | (0,7)
1 | 3 | 14 | (0,7)
2 | 4 | 15 | (0,8)
2 | 5 | 15 | (0,9)
3 | 6 | 16 | (0,10)
(12 rows)
Newer version (I added an Id column to the table)
-- for convenience :set of all adjacent nodes.
CREATE TEMP VIEW pair AS
SELECT one.id AS one
, two.id AS two
FROM graph one
JOIN graph two ON (one.account_id = two.account_id OR one.device_id = two.device_id)
AND one.id <> two.id
;
WITH RECURSIVE flood AS (
SELECT g.id, g.id AS parent_id
, 0 AS lev
, ARRAY[g.id]AS arr
FROM graph g
UNION ALL
SELECT c.id, p.parent_id AS parent_id
, 1+p.lev AS lev
, p.arr || ARRAY[c.id] AS arr
FROM graph c
JOIN flood p ON EXISTS (
SELECT * FROM pair WHERE p.id = pair.one AND c.id = pair.two)
AND p.parent_id < c.id
AND NOT p.arr #> ARRAY[c.id] -- avoid cycles/loops
)
SELECT g.*, a.parent_id
, dense_rank() over (ORDER by a.parent_id)AS group_id
FROM graph g
JOIN (SELECT id, MIN(parent_id) AS parent_id
FROM flood
GROUP BY id
) a
ON g.id = a.id
ORDER BY a.parent_id, g.id
;
New results:
CREATE VIEW
id | account_id | device_id | parent_id | group_id
----+------------+-----------+-----------+----------
1 | 1 | 10 | 1 | 1
2 | 1 | 11 | 1 | 1
3 | 1 | 12 | 1 | 1
4 | 2 | 10 | 1 | 1
5 | 3 | 11 | 1 | 1
6 | 3 | 13 | 1 | 1
7 | 3 | 14 | 1 | 1
8 | 4 | 15 | 8 | 2
9 | 5 | 15 | 8 | 2
10 | 6 | 16 | 10 | 3
(10 rows)

PostgreSQL - Detecting patterns in a series

Consider the following table:
id | date | status
1 | 2014-01-10 | 1
1 | 2014-02-10 | 1
1 | 2014-03-10 | 1
1 | 2014-04-10 | 1
1 | 2014-05-10 | 0
1 | 2014-06-10 | 0
------------------------
2 | 2014-01-10 | 1
2 | 2014-02-10 | 1
2 | 2014-03-10 | 0
2 | 2014-04-10 | 1
2 | 2014-05-10 | 0
2 | 2014-06-10 | 0
------------------------
3 | 2014-01-10 | 1
3 | 2014-02-10 | 0
3 | 2014-03-10 | 0
3 | 2014-04-10 | 1
3 | 2014-05-10 | 0
3 | 2014-06-10 | 0
------------------------
4 | 2014-01-10 | 0
4 | 2014-02-10 | 1
4 | 2014-03-10 | 1
4 | 2014-04-10 | 1
4 | 2014-05-10 | 0
4 | 2014-06-10 | 0
------------------------
5 | 2014-01-10 | 0
5 | 2014-02-10 | 1
5 | 2014-03-10 | 0
5 | 2014-04-10 | 1
5 | 2014-05-10 | 0
5 | 2014-06-10 | 0
------------------------
The Id field is the user id, the date field is when a certain checkpoint is due and the status indicates if the checkpoint is accomplished by its user.
I'm having a big trouble trying to detect users that skipped some checkpoint, like the users with ids 2, 3, 4 and 5. Actually I need a query that lists the id's that have a missing checkpoint in the middle or start of the series, returning only the ids.
I've tried hard to find a way of doing that just with queries, but I couldn't create one. I know that I could do it coding some script, but that project I'm working on requires that I do it just using SQL.
Anyone have a slightest idea on how to accomplish that ?
EDIT: As recommended by the mods here are more details and some things I unsuccessfully tried:
My most successful try was to count how many statuses were registered for each id with this query:
SELECT
id,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS check,
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS non_check
FROM
example_table
GROUP BY
id
ORDER BY
id
Getting the following result:
id | check | non_check
1 | 4 | 2
2 | 3 | 3
3 | 2 | 4
4 | 3 | 3
5 | 2 | 4
With that result I could select each id entries limiting by it's check result doing a SUM on the status field, if the SUM result is equal with the check result then the checkpoint is contiguous, like in:
WITH tbl AS (
SELECT id, status, SUM(status) AS "sum"
FROM (
SELECT id, status FROM example_table WHERE id = 1 ORDER BY date LIMIT 4
) AS tbl2
GROUP BY
status,id
)
SELECT
id,"sum"
FROM
tbl
WHERE
status = 1
Getting the following result:
id | sum
1 | 4
As the sum result is equal to check on the first query, I can determine that the checkpoints are contiguous. But take the id 2 as an example this time, it's query is:
WITH tbl AS (
SELECT id, status, SUM(status) AS "sum"
FROM (
SELECT id, status FROM example_table WHERE id = 2 ORDER BY date LIMIT 3
) AS tbl2
GROUP BY
status,id
)
SELECT
id,"sum"
FROM
tbl
WHERE
status = 1
Notice that I changed the id on WHERE and the LIMIT values based on which id I'm working with and its check result on the first query, and I got the following result:
id | sum
2 | 2
As the sum field value for id 2 in that query differs from its check value, I can say it's not contiguous. That pattern can be repeated with every id.
As I said before, to work that problem out that way I would need to do it by code, but in that specific case I need it to be in SQL.
Also I found the following article:
postgres detect repeating patterns of zeros
In which the problem resembles mine, but he wants to detect repeating zeroes, it has enlighten me a bit, but not enough to solve my own problem.
Thanks in advance!
The pattern you're looking for is a missed checkpoint followed by an accomplished checkpoint. Join each checkpoint from a user with the next (by timestamp) checkpoint then look for status 0 joined to status 1.
Here is an example:
create table tab (id int,date date,status int);
insert into tab values(1 , '2014-01-10' , 1),(1 , '2014-02-10' , 1),(1 , '2014-03-10' , 1),(1 , '2014-04-10' , 1),(1 , '2014-05-10' , 0),(1 , '2014-06-10' , 0),(2 , '2014-01-10' , 1),(2 , '2014-02-10' , 1),(2 , '2014-03-10' , 0),(2 , '2014-04-10' , 1),(2 , '2014-05-10' , 0),(2 , '2014-06-10' , 0),(3 , '2014-01-10' , 1),(3 , '2014-02-10' , 0),(3 , '2014-03-10' , 0),(3 , '2014-04-10' , 1),(3 , '2014-05-10' , 0),(3 , '2014-06-10' , 0),(4 , '2014-01-10' , 0),(4 , '2014-02-10' , 1),(4 , '2014-03-10' , 1),(4 , '2014-04-10' , 1),(4 , '2014-05-10' , 0),(4 , '2014-06-10' , 0),(5 , '2014-01-10' , 0),(5 , '2014-02-10' , 1),(5 , '2014-03-10' , 0),(5 , '2014-04-10' , 1),(5 , '2014-05-10' , 0),(5 , '2014-06-10' , 0);
with tabwithrow as
(select *
, row_number() OVER(PARTITION by id order by date) rnum
from tab)
select *
from tabwithrow a
join tabwithrow b on b.rnum = a.rnum + 1
and a.id = b.id
and a.status = 0
and b.status = 1;

Alternate of lead lag function in SQL Server 2008

I want to compare the current row with a value in the next row. SQL has LEAD and LAG functions to get the next and previous values but I can not use them because I am using SQL Server 2008.
So how do I get this?
I have table with output
+----+-------+-----------+-------------------------+
| Id | ActId | StatusId | MinStartTime |
+----+-------+-----------+-------------------------+
| 1 | 42 | 1 | 2014-02-14 11:17:21.203 |
| 2 | 42 | 1 | 2014-02-14 11:50:19.367 |
| 3 | 42 | 1 | 2014-02-14 11:50:19.380 |
| 4 | 42 | 6 | 2014-02-17 05:25:57.280 |
| 5 | 42 | 6 | 2014-02-19 06:09:33.150 |
| 6 | 42 | 1 | 2014-02-19 06:11:24.393 |
| 7 | 42 | 6 | 2014-02-19 06:11:24.410 |
| 8 | 42 | 8 | 2014-02-19 06:44:47.070 |
+----+-------+-----------+-------------------------+
What I want to do is if the current row status is 1 and the next row status is 6 and both times are the same (up to minutes) then I want to get the row where the status is 1.
Eg: Id 6 row has status 1 and Id 7 row has status 6 but both times are the same ie. 2014-02-19 06:11
So I want to get this row or id for status 1 ie. id 6
In your case, the ids appear to be numeric, you can just do a self-join:
select t.*
from table t join
table tnext
on t.id = tnext.id - 1 and
t.StatusId = 1 and
tnext.StatusId = 6 and
datediff(second, t.MinStartTime, tnext.MinStartTime) < 60;
This isn't quite the same minute. It is within 60 seconds. Do you actually need the same calendar time minute? If so, you can do:
select t.*
from table t join
table tnext
on t.id = tnext.id - 1 and
t.StatusId = 1 and
tnext.StatusId = 6 and
datediff(second, t.MinStartTime, tnext.MinStartTime) < 60 and
datepart(minute, t.MinStartTime) = datepart(minute, tnext.MinStartTime);
Just posting a more complex join using two different tables created with Gordon's foundation. Excuse the specific object names, but you'll get the gist. Gets the percentage change in samples from one to the next.
SELECT
fm0.SAMPLE curFMSample
, fm1.SAMPLE nextFMSample
, fm0.TEMPERATURE curFMTemp
, fm1.TEMPERATURE nextFMTemp
, ABS(CAST((fm0.Temperature - fm1.Temperature) AS DECIMAL(4, 0)) / CAST(fm0.TEMPERATURE AS DECIMAL(4, 0))) AS fmTempChange
, fm0.GAUGE curFMGauge
, fm1.GAUGE nextFMGauge
, ABS(CAST((fm0.GAUGE - fm1.GAUGE) AS DECIMAL(4, 4)) / CAST(fm0.GAUGE AS DECIMAL(4, 4))) AS fmGaugeChange
, fm0.WIDTH curFMWidth
, fm1.WIDTH nextFMWidth
, ABS(CAST((fm0.Width - fm1.Width) AS DECIMAL(4, 2)) / CAST(fm0.Width AS DECIMAL(4, 2))) AS fmWidthChange
, cl0.TEMPERATURE curClrTemp
, cl1.TEMPERATURE nextClrTemp
, ABS(CAST((cl0.Temperature - cl1.Temperature) AS DECIMAL(4, 0)) / CAST(cl0.TEMPERATURE AS DECIMAL(4, 0))) AS clrTempChange
FROM
dbo.COIL_FINISHING_MILL_EXIT_STR02 fm0
INNER JOIN dbo.COIL_FINISHING_MILL_EXIT_STR02 fm1 ON (fm0.SAMPLE = fm1.SAMPLE - 1 AND fm1.coil = fm0.coil)
INNER JOIN dbo.COIL_COILER_STR02 cl0 ON fm0.coil = cl0.coil AND fm0.SAMPLE = cl0.SAMPLE
INNER JOIN dbo.COIL_COILER_STR02 cl1 ON (cl0.SAMPLE = cl1.SAMPLE - 1 AND cl1.coil = cl0.coil)
WHERE
fm0.coil = 2015515872
Well, I would suggest a very simple solution if you do not have a sequential row id but a different step (if some records were deleted for example..):
declare #t table(id int, obj_name varchar(5))
insert #t select 1,'a'
insert #t select 5,'b'
insert #t select 22,'c'
insert #t select 543,'d'
---------------------------------
select *from #t
Example Source Table #t:
---------------------------------
id obj_name
1 a
5 b
22 c
543 d
---------------------------------
Select with self join
select obj_name_prev=tt.obj_name, obj_name_next=min(t.obj_name)
from #t t
join #t tt on tt.id < t.id
group by tt.obj_name
Result:
---------------------------------
obj_name_prev obj_name_next
a b
b c
c d
---------------------------------

Selecting the same row multiple times

I have a table that has some children of a master object. Any child can occur more than once, and there is a Occurences column that contains that number, so the data in the table is something like:
ChildID | ParentID | Occurences
-------------------------------
1 | 1 | 2
2 | 1 | 2
3 | 2 | 1
4 | 2 | 3
I need to get a list of all the children, with each child appearing the corect number of times in the result, something like
IDENT | ChildID | ParentID
--------------------------
1 | 1 | 1
2 | 1 | 1
3 | 2 | 1
4 | 2 | 1
5 | 3 | 2
6 | 4 | 2
7 | 4 | 2
8 | 4 | 2
I can do this with a cursor that loops the table and inserts as many rows as neccessary, but I don't think that that is the best solution possible.
Thanks for the help
Create script included:
DECLARE #Children TABLE (ChildID int, ParentID int, Occurences int)
INSERT #Children
SELECT 1, 1, 2 UNION ALL
SELECT 2, 1, 2 UNION ALL
SELECT 3, 2, 1 UNION ALL
SELECT 4, 2, 3
;with C as
(
select ChildID,
ParentID,
Occurences - 1 as Occurences
from #Children
union all
select ChildID,
ParentID,
Occurences - 1 as Occurences
from C
where Occurences > 0
)
select row_number() over(order by ChildID) as IDENT,
ChildID,
ParentID
from C
order by IDENT
;WITH CTEs
AS
(
SELECT 1 [Id]
UNION ALL
SELECT [Id] + 1 FROM CTEs WHERE [Id] < 100
)
SELECT ROW_NUMBER() OVER(ORDER BY c1.ChildID, c1.ParentID) [rn]
, c1.ChildID, c1.ParentID
FROM CTEs ct
JOIN #Children c1 ON c1.Occurences >= ct.[Id]
Another way to generate sequence is using predefined table, e.g. master.dbo.spt_values:
SELECT ROW_NUMBER() OVER(ORDER BY c1.ChildID, c1.ParentID) [rn]
, c1.ChildID, c1.ParentID
FROM master.dbo.spt_values ct
JOIN #Children c1 ON c1.Occurences > ct.number
AND ct.type = 'P'