Since OP asked for a Procedure-Less-Solution for multiple root-nodes after resolving the ambiguous question, the part right below was added. It represents an experimental addition to the previous answer, that was based on the "one root idea", see below.
Semi-Automatic N-Roots solution attempt without the use of Procedures
Why Semi-Automatic?
Because you have to know the level of nesting beforehand and you would also have to adjust the numbers of helper-tables that you want to use including altering the final 'absparent' table update logic.
How does it work?
The idea behind this approach is that we first identify the root nodes and save them to a table (t1).
Next, we want to find the nodes that sit one layer below the root (t2) - we already store the children along in (t1). So we fill the (t2) table with the information provided from (t1) and the main table (absparent).
We then repeat this for all n-layers that should be supported.
In the update-logic, we connect the helper-tables and find the (t1) root node i.e. the ultimate parent.
I used https://sqliteonline.com for testing.
The output would look like this:
Table t1:
id |
parent |
child |
1 |
A |
B |
4 |
E |
F |
Table t2:
id |
parent |
child |
2 |
B |
C |
5 |
F |
G |
Table t3:
Table 'absparent':
parent |
child |
ultimate_parent |
A |
B |
A |
B |
C |
A |
C |
D |
A |
E |
F |
E |
F |
G |
E |
--
-- NODE FLATTENING, N-LEVELS
--
-- delete old data
DROP TABLE IF EXISTS `absparent`;
DROP TABLE IF EXISTS `t1`;
DROP TABLE IF EXISTS `t2`;
DROP TABLE IF EXISTS `t3`;
-- create main table
CREATE TABLE `absparent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent` varchar(3) DEFAULT NULL,
`child` varchar(3) DEFAULT NULL,
`ultimate_parent` varchar(3) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `absparent` (parent, child)
VALUES
("A", "B"),
("B", "C"),
("C", "D"),
("E", "F"),
("F", "G");
-- create temp helper tables
CREATE TABLE `t1` (id INT(11), parent VARCHAR(3), child VARCHAR(3));
INSERT INTO `t1`
SELECT id, parent, child
FROM `absparent`
WHERE parent
NOT IN (SELECT DISTINCT child FROM `absparent`);
CREATE TABLE `t2` (id INT(11), parent VARCHAR(3), child VARCHAR(3));
INSERT INTO `t2`
SELECT a.id, a.parent, a.child FROM `absparent` a, `t1` WHERE a.parent = t1.child;
CREATE TABLE `t3` (id INT(11), parent VARCHAR(3), child VARCHAR(3));
INSERT INTO `t3`
SELECT a.id, a.parent, a.child FROM `absparent` a, `t2` WHERE a.parent = t2.child;
-- update absparent table with the temp helper tables
UPDATE absparent a, t1, t2, t3
SET a.ultimate_parent = t1.parent
WHERE
(a.parent = t1.parent) OR
(a.parent = t2.parent AND t1.child = t2.parent) OR
(a.parent = t3.parent AND t1.child = t2.parent AND t2.child = t3.parent);
-- final output
SELECT * FROM absparent;
-- remove temp tables
DROP TABLE `t1`;
DROP TABLE `t2`;
DROP TABLE `t3`;
One root only
Under the premise that E is connected to the rest of the nodes and you only have one ultimate parent (has no parent node!) in the entire table, you could probably do something like this:
Create the DB:
CREATE TABLE `absparent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent` varchar(3) DEFAULT NULL,
`child` varchar(3) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Insert the records:
INSERT INTO absparent (parent, child)
VALUES
("A", "B"),
("B", "C"),
("C", "D"),
("E", "F"),
("F", "G"),
("C", "E")
Run the query with a subquery:
The ultimate parent has to be a node that has no parent node i.e. is never a child of another node.
SELECT
parent,
child,
(
SELECT parent FROM absparent
WHERE parent
NOT IN (SELECT DISTINCT child FROM absparent)
) AS ultimate_parent
FROM absparent
Output:
parent |
child |
ultimate_parent |
A |
B |
A |
B |
C |
A |
C |
D |
A |
E |
F |
A |
F |
G |
A |
C |
E |
A |