如何树形结构的数据

616 阅读3分钟

我们讨论一下,具有树形特征的一类数据,比如组织架构,菜单等,如何将这一类数据放到数据库中存储?

 1 级架构
 1.1 二级架构
 1.1.1 三级架构
 1.2 二级架构
 1.2.1 三级架构
 1.2.1.1 四级架构
 ...

那么这个问题难点在于哪呢?

  1. 组织架构层级不定,导致我们没办法将这些数据按每层一个表的形式存起来。就算固定层级的组织架构,每一层的大部分属性都一样,分表存储显然比较浪费
  2. 上下级之间的关联关系如何存储。很多人看到这个问题的第一反应是:将子层级的id存粗到父层级的字段中。因为这样最符合直观印象。
public class Organization{
    private int id;
    private String name;
    private List<Organization> subOrgs;
}

但是无论存一个字段还是多个字段好像都不合适。存一个字段增删改都是问题,从子菜单向父菜单查也会是问题。存多个字段的话,到底多少个合适呢?

基于这两点,我们可以得到一个结论:组织架构数据适合存在一个表中,关联关系不宜存在父层级的数据行上。不存在父层级的数据行上就存在子层级的行上吧。

create table t_organization{
    id int(8) comment '组织ID',
    name varchar(64) comment '组织名称',
    parent_id int(8) comment '上级组织ID'
}

使用这个表结构,我们查询树根的时候,可以使用

    select * from t_organization where parent_id is null;

查询一个组织的子组织的时候,可以有:

    select * from t_organization where parent_id = xxx;

一般的常规的需求就能解决了。

但是事情总不是那么简单。变态的需求一个接一个过来

  • 要查询一个组织下所有的子组织,用来做统计。
  • 如何快速找到一个节点的根节点

这两个问题本质都是相同的:如何找到和某一个节点有间接关系但是没直接关系的节点?

找到所有子节点的代码可以是


    publc List<Organization> allSubs(int rootId){
        List<Organization> allSubOrgs=new ArrayList<>();
        List<Organization> subOrgs=organizationDao.findSubOrgs(rootId);
        allSubOrgs.addAll(subOrgs);
        for(Organization org:subOrgs){
            allSubOrgs.addAllallSubs(org.getId()));
        }
        return allSubOrgs;
    }

找到根节点的代码可以是

     publc Organization rootOrg(int currentId){
        Organization current= organizationDao.getById(currentId);
        if(current.getParentId==null){
            return current;
        }else{
            return rootOrg(current.getParentId());
        }
    }

学了这么多年的递归,终于能派上用场了~ 但是一考虑到要多次查库,似乎这种设计也没那么好。

那么还有没有其他的办法解决呢?有!事实上有一种叫做闭包表的设计方案,可以解决这种问题。


    create table t_organization(
        id int(8) comment '组织ID',
        name varchar(64) comment '组织名称'
    );
    
    create table t_org_rela(
        ancestor_id int(8) comment '祖先id',
        node_id int(8) comment '当前节点id',
        distance int(8) comment '与祖先节点的距离',
    );

存储数据的时候就可以有

组织架构:

id name
1 国务院
2 北京市政府
3 河北省政府
4 朝阳区政府
5 石家庄市政府

关系表

ancestor_id node_id distance
1 2 1
1 3 1
1 4 2
1 5 2
2 4 1
3 5 1

可以看出这是一种拿空间换时间的方案。 我们查询直接子组织的时候

select a.* from t_organization a,t_org_rela b where b.ancestor_id= x and b.distance=1 and b.
node_id=a.id ;

查询所有子节点的时候:

select a.* from t_organization a,t_org_rela b where b.ancestor_id= x and b.
node_id=a.id ;

查询根节点的时候

  select a.* from t_organization t,t_org_rela a 
  left join  
  (select max(distance) distance,node_id from t_org_rela where node_id = x group by node_id) b on a.node_id= b.node_id and a. distance=b.distance where t.id=a.ancestor_id;

插入一个关联关系的时候

insert into t_org_rela(ancestor_id,node_id,distance) select 
ancestor_id, 'x' ,distance+1 from t_org_rela where node_id= 'parent';
insert into t_org_rela(ancestor_id,node_id,distance) values('parent','x',1) ;

可以看出来,闭包表解决了多次查询数据库的问题,但是带来了程序复杂度的提升,所以在要求不是很严苛的时候,闭包表不是一个很好的选择。