mysql 存储树形结构(非遍历方法查找树结构)

树结构如下:

测试数据树结构图

查询节点为 p,已知 p 节点id,探究 分别 查询 p 节点的 父节点,祖父节点,子节点,子孙节点的 复杂程度,
以及节点移动(p移动到b 节点下面)、节点删除(删除p节点,p节点子节点跟进,为d的子节点)、节点新增(p节点添加子节点 w)

原设计方案(Adjacency list 邻接表):

每一条记录存parent_id

  1. 表结构:
    <pre>
    CREATE TABLE folder (
    id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id,自增',
    folderName varchar(100) NOT NULL COMMENT '文件名',
    parentId int(11) NOT NULL COMMENT '父节点id',
    PRIMARY KEY (id),
    INDEX idx_parent_id (parentId ASC)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '文件夹表';
    </pre>
  2. 测试数据
    <pre>
    INSERT INTO folder (id,folderName,parentId)VALUES (1,'a',0);
    INSERT INTO folder (id,folderName,parentId)VALUES (2,'b',0);
    INSERT INTO folder (id,folderName,parentId)VALUES (3,'c',0);
    INSERT INTO folder (id,folderName,parentId)VALUES (4,'d',1);
    INSERT INTO folder (id,folderName,parentId)VALUES (5,'e',1);
    INSERT INTO folder (id,folderName,parentId)VALUES (6,'f',1);
    INSERT INTO folder (id,folderName,parentId)VALUES (7,'x',2);
    INSERT INTO folder (id,folderName,parentId)VALUES (8,'y',2);
    INSERT INTO folder (id,folderName,parentId)VALUES (9,'z',2);
    INSERT INTO folder (id,folderName,parentId)VALUES (10,'p',4);
    INSERT INTO folder (id,folderName,parentId)VALUES (11,'q',4);
    INSERT INTO folder (id,folderName,parentId)VALUES (12,'r',4);
    INSERT INTO folder (id,folderName,parentId)VALUES (13,'t',5);
    INSERT INTO folder (id,folderName,parentId)VALUES (14,'u',10);
    INSERT INTO folder (id,folderName,parentId)VALUES (15,'v',14);
    </pre>
  3. 查询语句
  • p 节点的父节点
    <pre>
    select * from folder where id=(select parentId from folder where id=10);
    </pre>

  • p 节点 祖父节点
    <pre>
    DROP FUNCTION IF EXISTS getParentList;
    DELIMITER //
    CREATE FUNCTION getParentList(rootId varchar(100))
    RETURNS varchar(1000)
    BEGIN
    DECLARE fid varchar(100) default '';
    DECLARE str varchar(1000) default rootId;
    WHILE rootId is not null do
    SET fid =(SELECT parentId FROM folder WHERE id = rootId);
    IF fid is not null THEN
    SET str = concat(str, ',', fid);
    SET rootId = fid;
    ELSE
    SET rootId = fid;
    END IF;
    END WHILE;
    return str;
    END //
    DELIMITER ;
    select * from folder where FIND_IN_SET(id,getParentList(10))
    </pre>

  • p 节点 子节点
    <pre>
    select * from folder where parentId=10;
    </pre>

  • p 节点 子孙节点
    <pre>
    DROP FUNCTION IF EXISTSgetChildList; DELIMITER // CREATE FUNCTIONgetChildList`(rootId INT)
    RETURNS varchar(1000) READS SQL DATA
    BEGIN
    DECLARE sTemp VARCHAR(1000);
    DECLARE sTempChd VARCHAR(1000);
    SET sTemp = '$';
    SET sTempChd =cast(rootId as CHAR);
    WHILE sTempChd is not null DO
    SET sTemp = concat(sTemp,',',sTempChd);
    SELECT group_concat(id) INTO sTempChd FROM folder where FIND_IN_SET(parentId,sTempChd)>0;
    END WHILE;
    RETURN sTemp;
    END //
    DELIMITER ;
    select *from folder where find_in_set(id, getChildList(10));
    </pre>

  • 节点新增(p节点添加子节点 w)
    INSERT INTO folder (folderName,parentId)VALUES ('w',10);

  • 节点移动(p移动到b 节点下面,p 节点子孙节点跟随)
    <pre>
    UPDATE folder f, ( SELECT * FROM folder WHERE folderName = 'b' ) temp
    SET f.parentId = temp.id
    WHERE f.id = 10;
    </pre>

  • 节点删除(删除p节点,p节点子节点 u 跟进,为d的子节点)
    <pre>
    update folder f,(select * from folder where folderName='p') temp
    set f.parentId=temp.parentId
    where f.parentId=10;
    delete from folder where id=10;
    </pre>

  • 获取整棵树结构(需要知道 树 层级结构)
    <pre>
    SELECT t1.folderName AS lev1, t2.folderName as lev2, t3.folderName as lev3, t4.folderName as lev4,t5.folderName as lev5
    FROM folder AS t1
    LEFT JOIN folder AS t2 ON t2.parentId = t1.id
    LEFT JOIN folder AS t3 ON t3.parentId = t2.id
    LEFT JOIN folder AS t4 ON t4.parentId = t3.id
    LEFT JOIN folder AS t5 ON t5.parentId = t4.id
    WHERE t1.folderName = 'a';
    </pre>


    邻接表树数据图

路径表(Path Enumeration)

每一条记录存整个tree path经过的node枚举

  1. 表结构:
    <pre>
    CREATE TABLE path_folder (
    id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键id,自增',
    folderName VARCHAR(100) NOT NULL COMMENT '文件夹名称',
    path VARCHAR(200) NOT NULL COMMENT '路径',
    parentId INT(11) NOT NULL DEFAULT 0 COMMENT '父节点id'
    PRIMARY KEY (id))
    ENGINE = InnoDB
    DEFAULT CHARSET = utf8mb4
    COMMENT = '路径文件夹表';
    </pre>
  2. 测试数据
    <pre>
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (1,'a','/0',0);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (2,'b','/0',0);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (3,'c','/0',0);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (4,'d','/1',1);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (5,'e','/1',1);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (6,'f','/1',1);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (7,'x','/2',2);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (8,'y','/2',2);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (9,'z','/2',2);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (10,'p','/1/4',4);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (11,'q','/1/4',4);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (12,'r','/1/4',4);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (13,'t','/1/5',5);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (14,'u','/1/4/10',10);
    INSERT INTO path_folder (id,folderName,path,parentId)VALUES (15,'v','/1/4/10/14',14);
    </pre>
  3. 查询语句
  • p 节点的父节点
    <pre>
    select * from path_folder where id=(select parentId from path_folder where id=10);
    </pre>
  • p 节点 祖父节点
    <pre>
    SELECT *
    FROM path_folder
    WHERE id IN (
    SELECT substring_index(substring_index(t.path, '/', b.help_topic_id + 1), '/', -1)
    FROM path_folder t
    JOIN mysql.help_topic b ON b.help_topic_id < LENGTH(t.path) - LENGTH(REPLACE(t.path, '/', '')) + 1
    WHERE id = 10
    );
    </pre>

字符串切割参考文章

  • p 节点 子节点
    <pre>
    select * from path_folder where parentId=10;
    </pre>

  • p 节点 子孙节点
    <pre>
    SELECT * FROM path_folder
    WHERE path LIKE '%/10%';
    </pre>

  • 节点新增(p节点添加子节点 w)
    <pre>
    INSERT INTO path_folder (folderName, path, parentId)
    SELECT 'w', concat((
    SELECT path
    FROM path_folder
    WHERE id = 10
    ), '/10'), 10;
    </pre>

  • 节点移动(p移动到b 节点下面,p 节点子孙节点跟随)
    <pre>
    UPDATE path_folder pf, (
    SELECT *
    FROM path_folder
    WHERE folderName = 'b'
    ) b, (
    SELECT *
    FROM path_folder
    WHERE id = 10
    ) p
    SET pf.path = replace(pf.path, p.path, concat(if(b.path = '/0', '', b.path), '/', b.id))
    WHERE pf.path LIKE concat('%', p.path, '/%')
    OR pf.id = 10;
    UPDATE path_folder f, ( SELECT * FROM path_folder WHERE folderName = 'b' ) temp
    SET f.parentId = temp.id
    WHERE f.id = 10;
    </pre>

  • 节点删除(删除p节点,p节点子节点 u 跟进,为d的子节点)
    <pre>
    update path_folder f,(select * from path_folder where folderName='p') temp
    set f.parentId=temp.parentId
    where f.parentId=10;
    update path_folder
    set path=replace(path,'/10','')
    where path like "%/10%";
    delete from folder where id=10;
    </pre>

嵌套集(Nested Sets)

每一条记录存 nleft 和 nright
此处不适合当前业务,故不展开讨论,附相关博客地址,有需求可自行参考


树形结构的数据库表Schema设计


基于左右值编码的树形数据库表结构设计


在MySQL中存储树状结构

路径表(Closure Table)

维护一个表,所有的tree path作为记录进行保存

  1. 表结构
    <pre>
    CREATE TABLE closure_path (
    id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键id,自增',
    folderName VARCHAR(100) NOT NULL COMMENT '文件夹名称',
    PRIMARY KEY (id))
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = utf8mb4
    COMMENT = '路径表';
    </pre>
    <pre>
    CREATE TABLE closure_path_relation (
    id INT NOT NULL AUTO_INCREMENT COMMENT '主键id,自增',
    ancestor INT(1) NOT NULL COMMENT '祖先id',
    descendant INT(11) NOT NULL COMMENT '后裔',
    isLeaf TINYINT(11) NOT NULL COMMENT '是否为叶子节点',
    depth INT(6) NOT NULL DEFAULT 0 COMMENT '深度',
    PRIMARY KEY (id))
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = utf8mb4
    COMMENT = '路径关系表';
    </pre>

  2. 测试数据
    <pre>
    INSERT INTO closure_path (id,folderName)VALUES (1,'a');
    INSERT INTO closure_path (id,folderName)VALUES (2,'b');
    INSERT INTO closure_path (id,folderName)VALUES (3,'c');
    INSERT INTO closure_path (id,folderName)VALUES (4,'d');
    INSERT INTO closure_path (id,folderName)VALUES (5,'e');
    INSERT INTO closure_path (id,folderName)VALUES (6,'f');
    INSERT INTO closure_path (id,folderName)VALUES (7,'x');
    INSERT INTO closure_path (id,folderName)VALUES (8,'y');
    INSERT INTO closure_path (id,folderName)VALUES (9,'z');
    INSERT INTO closure_path (id,folderName)VALUES (10,'p');
    INSERT INTO closure_path (id,folderName)VALUES (11,'q');
    INSERT INTO closure_path (id,folderName)VALUES (12,'r');
    INSERT INTO closure_path (id,folderName)VALUES (13,'t');
    INSERT INTO closure_path (id,folderName)VALUES (14,'u');
    INSERT INTO closure_path (id,folderName)VALUES (15,'v');
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,1,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (2,2,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (3,3,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (4,4,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (5,5,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (6,6,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (7,7,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (8,8,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (9,9,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (10,10,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (11,11,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (12,12,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (13,13,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (14,14,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (15,15,0,0);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,4,0,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,5,0,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,6,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,10,0,2);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,11,0,2);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,12,0,2);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,13,1,2);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,14,0,3);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (1,15,1,4);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (2,7,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (2,8,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (2,9,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (5,13,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (4,10,0,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (4,11,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (4,12,1,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (4,14,0,2);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (4,15,1,4);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (10,14,0,1);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (10,15,1,2);
    INSERT INTO closure_path_relation (ancestor,descendant,isLeaf,depth)VALUES (14,15,1,1);
    </pre>

  3. 查询语句

  • p 节点的父节点
    <pre>
    select * from closure_path
    where id in(
    select ancestor from closure_path_relation
    where descendant=10 and depth=1);
    </pre>

  • p 节点 祖父节点
    <pre>
    select * from closure_path
    where id in(
    select ancestor from closure_path_relation
    where descendant=10 and depth!=0);
    </pre>

  • p 节点 子节点
    <pre>
    select * from closure_path
    where id in(
    select descendant from closure_path_relation
    where ancestor=10 and depth=1);
    </pre>

  • p 节点 子孙节点
    <pre>
    select * from closure_path
    where id in(
    select descendant from closure_path_relation
    where ancestor=10 and depth!=0);
    </pre>

  • 节点新增(p节点添加子节点 w)
    <pre>
    insert into closure_path(folderName) values('w');
    insert into closure_path_relation(ancestor,descendant,isLeaf,depth)
    select ancestor,(select id from closure_path where folderName ='w'),1,(depth+1) from closure_path_relation
    where descendant=10;
    insert into closure_path_relation(ancestor,descendant,isLeaf,depth)
    select id,id,1,0 from closure_path
    where folderName='w';
    </pre>

  • 节点移动(p移动到b 节点下面,p 节点子孙节点跟随)
    // todo 待添加

  • 节点删除(删除p节点,p节点子节点 u 跟进,为d的子节点)
    <pre>
    update closure_path_relation
    set depth=depth-1
    where descendant in(select descendant from (select descendant from closure_path_relation
    where ancestor=10)temp);
    delete from closure_path_relation
    where descendant=10 or ancestor=10;
    delete from closure_path
    where id=20;
    </pre>

advantage && disvantage

优缺点对比图

popo先生的博客


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容