1. 问题描述:
工作中遇到一个需求,对于一个典型的递归层次分类结构表(组织机构表),要求根据某个节点值找到节点及其所有父级节点。
(1)示例表结构:
mysql> desc class; +-------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+-------------+------+-----+---------+-------+ | org_code | varchar(20) | NO | | NULL | | | parent_code | varchar(20) | YES | | NULL | | +-------------+-------------+------+-----+---------+-------+ 2 rows in set (0.00 sec)
(2)示例表数据:
mysql> select * from class; +--------------+-------------+ | org_code | parent_code | +--------------+-------------+ | 0001 | NULL | | 00010001 | 0001 | | 00010002 | 0001 | | 000100010001 | 00010001 | | 00010003 | 0001 | | 000100010002 | 0001 | +--------------+-------------+ 6 rows in set (0.00 sec)
2. 问题分析
不想专门加一个函数来做这件事,就想着用sql语句自身的能力来解决类似递归的查询。记得在《mysql技术内幕:sql编程》中解决间断区间问题的一个解决思路:通过自定义变量来记录上一行的值,那我是不是可以自定义变量记录上一个匹配行中父节点的值,进而找到所有匹配行呢?
貌似可以,但是这两个问题还是有本质不同的,关键区别是前者是一个有序数列,而我这里的数据是无序的字符串编码,所以我需要把它排个序,目的是让所有匹配行的父节点相关的行在其后面出现,这样我每次记录的新的父节点值就都能在接下来的行中找到啦,幸亏我这里的编码是有层次嵌套规则的,所以按照字符串降序就可以了。
(1)排序
mysql> select * from class order by org_code desc; +--------------+-------------+ | org_code | parent_code | +--------------+-------------+ | 00010003 | 0001 | | 00010002 | 0001 | | 000100010002 | 0001 | | 000100010001 | 00010001 | | 00010001 | 0001 | | 0001 | NULL | +--------------+-------------+ 6 rows in set (0.03 sec)
(2)逐行过滤记录,更新匹配值为匹配行的父节点
mysql> select (case when org_code=@r then org_code else null end) as code, -> (case when org_code=@r then @r:=parent_code else null end) as parent -> from (select org_code,parent_code from class order by org_code DESC) as c,(select @r:='000100010001') as t; +--------------+----------+ | code | parent | +--------------+----------+ | NULL | NULL | | NULL | NULL | | NULL | NULL | | 000100010001 | 00010001 | | 00010001 | 0001 | | 0001 | NULL | +--------------+----------+ 6 rows in set (0.03 sec)
(3)筛选匹配行
mysql> select code,parent from -> (select (case when org_code=@r then org_code else null end) as code, -> (case when org_code=@r then @r:=parent_code else null end) as parent -> from (select org_code,parent_code from class order by org_code DESC) as c,(select @r:='000100010001') as t) as tmp -> where code is not null; +--------------+----------+ | code | parent | +--------------+----------+ | 000100010001 | 00010001 | | 00010001 | 0001 | | 0001 | NULL | +--------------+----------+ 3 rows in set (0.00 sec)
(4)这里我要拿到一个in查询的匹配参数,所以使用group_concat来拼接查询结果,使用的时候再在两端拼上“(‘”和"')"就可以啦
mysql> select group_concat(code separator "','") as codes from -> (select (case when org_code=@r then org_code else null end) as code, -> (case when org_code=@r then @r:=parent_code else null end) as parent -> from (select org_code,parent_code from class order by org_code DESC) as c,(select @r:='000100010001') as t) as tmp -> where code is not null; +--------------------------------+ | codes | +--------------------------------+ | 000100010001','00010001','0001 | +--------------------------------+ 1 row in set (0.00 sec)