Hierarchy 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.
- Find Parents:
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:
- Find all Children
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)
CodePudding user response:
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 |