-- Based on the original articel at http://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclic-Graphs-DAG-o -- Here is a port to MySQL -- Edge table DROP TABLE IF EXISTS `Edge`; CREATE TABLE IF NOT EXISTS `Edge` ( `id` int(11) NOT NULL, `entry_edge_id` int(11) DEFAULT NULL COMMENT 'The ID of the incoming edge to the start vertex that is the creation reason for this implied edge; direct edges contain the same value as the Id column', `direct_edge_id` int(11) DEFAULT NULL COMMENT 'The ID of the direct edge that caused the creation of this implied edge; direct edges contain the same value as the Id column', `exit_edge_id` int(11) DEFAULT NULL COMMENT 'The ID of the outgoing edge from the end vertex that is the creation reason for this implied edge; direct edges contain the same value as the Id column', `start_vertex` varchar(50) NOT NULL COMMENT 'The ID of the start vertex', `end_vertex` varchar(50) NOT NULL COMMENT 'The ID of the end vertex', `hops` int(11) NOT NULL COMMENT 'Indicates how many vertex hops are necessary for the path; it is zero for direct edges', `source` varchar(50) NOT NULL COMMENT 'A column to indicate the context in which the graph is created; useful if we have more than one DAG to be represented within the same application CAUTION: you need to make sure that the IDs of vertices from different sources never clash; the best is probably use of UUIDs' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='DAG Edge table (http://goo.gl/awkHtB)'; ALTER TABLE `Edge` ADD PRIMARY KEY (`id`); ALTER TABLE `Edge` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- Procedure add_edge() DROP PROCEDURE IF EXISTS `add_edge`; DELIMITER $$ CREATE PROCEDURE `add_edge`(`param_start_vertex_id` VARCHAR(50), `param_end_vertex_id` VARCHAR(50), `param_source` VARCHAR(50)) proc_label:BEGIN DECLARE r INT; DECLARE NewId INT; IF EXISTS( SELECT id FROM Edge WHERE start_vertex = param_start_vertex_id AND end_vertex = param_end_vertex_id AND hops = 0 AND source = param_source) THEN LEAVE proc_label; END IF; IF param_start_vertex_id = param_end_vertex_id OR EXISTS (SELECT Id FROM Edge WHERE start_vertex = param_end_vertex_id AND end_vertex = param_start_vertex_id AND source = param_source) THEN LEAVE proc_label; END IF; INSERT INTO Edge ( start_vertex, end_vertex, hops, source) VALUES ( param_start_vertex_id, param_end_vertex_id, 0, param_source); SELECT LAST_INSERT_ID() INTO NewId; UPDATE Edge SET entry_edge_id = NewId , exit_edge_id = NewId , direct_edge_id = NewId WHERE Id = NewId; -- step 1: A's incoming edges to B INSERT INTO Edge ( entry_edge_id, direct_edge_id, exit_edge_id, start_vertex, end_vertex, hops, source) SELECT Id , NewId , NewId , start_vertex , param_end_vertex_id , hops + 1 , param_source FROM Edge WHERE end_vertex = param_start_vertex_id AND source = param_source; -- step 2: A to B's outgoing edges INSERT INTO Edge ( entry_edge_id, direct_edge_id, exit_edge_id, start_vertex, end_vertex, hops, source) SELECT NewId , NewId , Id , param_start_vertex_id , end_vertex , hops + 1 , param_source FROM Edge WHERE start_vertex = param_end_vertex_id AND source = param_source; -- step 3: A’s incoming edges to end vertex of B's outgoing edges INSERT INTO Edge ( entry_edge_id, direct_edge_id, exit_edge_id, start_vertex, end_vertex, hops, source) SELECT A.Id , NewId , B.Id , A.start_vertex , B.end_vertex , A.hops + B.hops + 1 , param_source FROM Edge AS A CROSS JOIN Edge AS B WHERE A.end_vertex = param_start_vertex_id AND B.start_vertex = param_end_vertex_id AND A.source = param_source AND B.source = param_source; END$$ DELIMITER ; -- Procedure remove_edge_id ~ remove by edge_id DROP PROCEDURE IF EXISTS `remove_edge_id`; DELIMITER $$ CREATE PROCEDURE `remove_edge_id`(`param_id` INT) proc_label:BEGIN DECLARE RowCount INT; IF NOT EXISTS (SELECT Id from Edge WHERE Id = param_id) THEN LEAVE proc_label; END IF; CREATE TABLE TempPurgeList (Id int); -- step 1: rows that were originally inserted with the first -- AddEdge call for this direct edge INSERT INTO TempPurgeList SELECT Id FROM Edge WHERE direct_edge_id = param_id; -- step 2: scan and find all dependent rows that are inserted afterwards label1: LOOP INSERT INTO TempPurgeList SELECT Id FROM Edge WHERE (hops > 0) AND ( entry_edge_id IN ( SELECT Id FROM TempPurgeList ) OR exit_edge_id IN ( SELECT Id FROM TempPurgeList ) ) AND (Id NOT IN (SELECT Id FROM TempPurgeList )); IF ROW_COUNT() = 0 THEN LEAVE label1; END IF; END LOOP label1; DELETE FROM Edge WHERE Id IN ( SELECT Id FROM TempPurgeList); DROP TABLE TempPurgeList; END$$ DELIMITER ; -- Procedure remove_edge DROP PROCEDURE IF EXISTS `remove_edge`; DELIMITER $$ CREATE PROCEDURE `remove_edge`(`param_start_vertex_id` VARCHAR(50), `param_end_vertex_id` VARCHAR(50), `param_source` VARCHAR(50)) BEGIN DECLARE ParamId INT; DECLARE CONTINUE HANDLER FOR NOT FOUND BEGIN END; SELECT Id INTO ParamId FROM Edge WHERE start_vertex = param_start_vertex_id AND end_vertex = param_end_vertex_id AND hops = 0 AND source = param_source; IF ParamId IS NOT NULL THEN CALL remove_edge_id(ParamId); END IF; END$$ DELIMITER ; -- Test TRUNCATE TABLE `Edge`; CALL add_edge('HelpDesk', 'Admins', 'AD'); CALL add_edge('Ali', 'Admins', 'AD'); CALL add_edge('Ali', 'Users', 'AD'); CALL add_edge('Burcu', 'Users', 'AD'); CALL add_edge('Can', 'Users', 'AD'); CALL add_edge('Managers', 'Users','AD'); CALL add_edge('Technicians', 'Users', 'AD'); CALL add_edge('Demet', 'HelpDesk', 'AD'); CALL add_edge('Engin', 'HelpDesk', 'AD'); CALL add_edge('Engin', 'Users', 'AD'); CALL add_edge('Fuat', 'Managers', 'AD'); CALL add_edge('G l', 'Managers', 'AD'); CALL add_edge('Hakan', 'Technicians', 'AD'); CALL add_edge('Irmak', 'Technicians', 'AD'); CALL add_edge('ABCTechnicians', 'Technicians', 'AD'); CALL add_edge('Jale', 'ABCTechnicians', 'AD'); SELECT start_vertex, hops FROM Edge WHERE end_vertex = 'Admins'; -- => return: -- start_vertex, hops -- HelpDesk, 0 -- Ali, 0 -- Demet, 1 -- Engin, 1 SELECT end_vertex, hops FROM Edge WHERE start_vertex = 'Jale'; -- => return: -- end_vertex, hops -- ABCTechnicians, 0 -- Technicians, 1 -- Users, 2