A hierarchical query with own nocycle solution will be presented. Improvement needed.
A tree with or without loops (Oidipus) is assumed. Table:
CREATE TABLE `person` (
`ID` varchar(10) NOT NULL,
`PARENT` varchar(10) NOT NULL,
`TYPE` varchar(10) NOT NULL,
`NAME` varchar(50) NOT NULL
)
Fields TYPE and NAME have no importance.Connection is realized with the ID of another person in field PARENT.
WITH recursive Parents(ID, SUMID, TYPE, PARENT, LEVEL) AS (
SELECT ID, Concat(ID,"Z"," ...") AS SUMID, TYPE, PARENT, 0 AS LEVEL FROM `person` WHERE ID = '1000000005'
UNION ALL
SELECT m.ID, CONCAT(m.ID,"Z",SUMID) AS SUMID , m.TYPE, m.PARENT, LEVEL + 1 FROM `person` as m INNER JOIN Parents t on m.ID = t.PARENT
WHERE LEVEL < 6
AND INSTR ( SUMID, m.ID) < 1
)
SELECT * FROM Parents;
An extra Column SUMID (concatenated "numeric" IDs, separator="Z") will be used for checking NOCCYCLE (see Oracle keyword). (Oidipus appears only one times in field ID). Works fine, but SUMID initial content should be coded as MAXLEVEL times 10 "String".
What only partially works:
WITH recursive Children(ID, SUMID, TYPE, PARENT, LEVEL) AS (
SELECT ID, Concat(ID,"Z"," ...") AS SUMID, TYPE, PARENT, 0 AS LEVEL FROM `person` WHERE ID = '1000000002'
UNION ALL
SELECT m.ID, CONCAT(m.ID,"Z",SUMID) AS SUMID , m.TYPE, m.PARENT, LEVEL + 1 FROM `person` as m INNER JOIN Children t on m.PARENT = t.ID
WHERE LEVEL < 6
AND INSTR ( SUMID, m.ID) < 1
)
SELECT * FROM Children;
When someone has 5 children and 5*5 = 25 grandchildren, etc., then SUMID could not be long enough. Furthermore, the script for all children is very slow and weak in performance. How could you implement "Find all children" in simple MySQL?
I tried to implement a hierarchical query for a tree rekordstructure. The query "Find Children" is slow and iefficient. Ia am hoping for improvement suggestions.
INSERT INTO `person` (`ID`, `PARENT`, `TYPE`, `NAME`) VALUES
('1000000001', '1000000001', 'A', 'first'),
('1000000002', '1000000004', 'B', 'second'),
('1000000003', '1000000002', 'C', 'third'),
('1000000004', '1000000002', 'C', 'fourth'),
('1000000005', '1000000004', 'C', 'fifth'),
('1000000006', '1000000002', 'C', '6th'),
('1000000007', '1000000002', 'C', '7th'),
('1000000008', '1000000002', 'C', '8th'),
('1000000009', '1000000002', 'C', '9th'),
('1000000010', '1000000005', 'D', '10th'),
('1000000011', '1000000005', 'D', '11th'),
('1000000012', '1000000005', 'D', '12nd'),
('1000000013', '1000000005', 'D', '13rd');
Result:
MariaDB [devmysql]> WITH recursive Children(ID, SUMID, TYPE, PARENT, LEVEL) AS (
-> SELECT ID, Concat(ID,"Z"," ") AS SUMID, TYPE, PARENT, 0 AS LEVEL FROM `person` WHERE ID = '1000000002'
-> UNION ALL
-> SELECT m.ID, CONCAT(m.ID,"Z",SUMID) AS SUMID , m.TYPE, m.PARENT, LEVEL + 1 FROM `person` as m INNER JOIN Children t on m.PARENT = t.ID
-> WHERE LEVEL < 6
-> AND INSTR ( SUMID, m.ID) < 1
-> )
-> SELECT * FROM Children;
+------------+-------------------------------+------+------------+-------+
| ID | SUMID | TYPE | PARENT | LEVEL |
+------------+-------------------------------+------+------------+-------+
| 1000000002 | 1000000002Z | B | 1000000004 | 0 |
| 1000000003 | 1000000003Z1000000002Z | C | 1000000002 | 1 |
| 1000000004 | 1000000004Z1000000002Z | C | 1000000002 | 1 |
| 1000000006 | 1000000006Z1000000002Z | C | 1000000002 | 1 |
| 1000000007 | 1000000007Z1000000002Z | C | 1000000002 | 1 |
| 1000000008 | 1000000008Z1000000002Z | C | 1000000002 | 1 |
| 1000000009 | 1000000009Z1000000002Z | C | 1000000002 | 1 |
| 1000000005 | 1000000005Z1000000004Z1000000 | C | 1000000004 | 2 |
| 1000000010 | 1000000010Z1000000005Z1000000 | D | 1000000005 | 3 |
| 1000000011 | 1000000011Z1000000005Z1000000 | D | 1000000005 | 3 |
| 1000000012 | 1000000012Z1000000005Z1000000 | D | 1000000005 | 3 |
| 1000000013 | 1000000013Z1000000005Z1000000 | D | 1000000005 | 3 |
+------------+-------------------------------+------+------------+-------+
12 rows in set, 11 warnings (0.004 sec)
The Oracle equivalent looks like that (see below) :
Hint
In oracle
WITH Children (ID, SUMID, LEVEL)
AS
(
SELECT
m.ID,
',' || CAST(m.ID AS VARCHAR(120) || ',' AS SUMID,
0 AS LEV
FROM
person AS m
WHERE
ID = '1000000002'
UNION ALL
SELECT
m.ID,
t.SUMID || m.ID || ',' AS SUMID,
LEV + 1 AS LEV
FROM
person AS m
INNER JOIN
Children AS t
ON m.PARENT = t.ID
WHERE
t.LEV < 10
AND INSTR(t.SUMID, CONCAT(',', m.ID, ',')) < 1
)
SELECT * FROM Children;
Query has delivered ca. 60000 records in 600 msecs.
Query has been tested against his connect by oracle counterpart with MINUS in both directions:
select ID
FROM person
START WITH ID = '1000000002'
CONNECT BY NOCYCLE PRIOR ID = PARENT
MINUS
SELECT ID FROM (
WITH Children (ID, SUMID, LEVEL)
AS
(
SELECT
m.ID,
',' || CAST(m.ID AS VARCHAR(120) || ',' AS SUMID,
0 AS LEV
FROM
person AS m
WHERE
ID = '1000000002'
UNION ALL
SELECT
m.ID,
t.SUMID || m.ID || ',' AS SUMID,
LEV + 1 AS LEV
FROM
person AS m
INNER JOIN
Children AS t
ON m.PARENT = t.ID
WHERE
t.LEV < 10
AND INSTR(t.SUMID, CONCAT(',', m.ID, ',')) < 1
)
SELECT * FROM Children);
Does not deliver any record.
Query was successfully tested in ORACLE in 1 sec. As for me Unbelievable quick.
"Na ja," Connect by can automatically deliver some more features:
As long as you don't need to output the LEVEL and the SUMID, you can use UNION instead of UNION ALL to prevent loops being evaluated.
CREATE TABLE `person` (
`ID` varchar(10) NOT NULL,
`PARENT` varchar(10) NOT NULL,
`TYPE` varchar(10) NOT NULL,
`NAME` varchar(50) NOT NULL
)
INSERT INTO `person` (`ID`, `PARENT`, `TYPE`, `NAME`) VALUES
('1000000001', '1000000001', 'A', 'first'),
('1000000002', '1000000004', 'B', 'second'),
('1000000003', '1000000002', 'C', 'third'),
('1000000004', '1000000002', 'C', 'fourth'),
('1000000005', '1000000004', 'C', 'fifth'),
('1000000006', '1000000002', 'C', '6th'),
('1000000007', '1000000002', 'C', '7th'),
('1000000008', '1000000002', 'C', '8th'),
('1000000009', '1000000002', 'C', '9th'),
('1000000010', '1000000005', 'D', '10th'),
('1000000011', '1000000005', 'D', '11th'),
('1000000012', '1000000005', 'D', '12nd'),
('1000000013', '1000000005', 'D', '13rd')
;
Records: 13 Duplicates: 0 Warnings: 0
WITH
RECURSIVE
Children
AS
(
SELECT * FROM `person` WHERE ID = '1000000002'
UNION
SELECT m.* FROM `person` as m INNER JOIN Children t on m.PARENT = t.ID
)
SELECT * FROM Children
ID | PARENT | TYPE | NAME |
---|---|---|---|
1000000002 | 1000000004 | B | second |
1000000003 | 1000000002 | C | third |
1000000004 | 1000000002 | C | fourth |
1000000006 | 1000000002 | C | 6th |
1000000007 | 1000000002 | C | 7th |
1000000008 | 1000000002 | C | 8th |
1000000009 | 1000000002 | C | 9th |
1000000005 | 1000000004 | C | fifth |
1000000010 | 1000000005 | D | 10th |
1000000011 | 1000000005 | D | 11th |
1000000012 | 1000000005 | D | 12nd |
1000000013 | 1000000005 | D | 13rd |