在数据库的设计里面,经常使用parentId字段来构建树型的表结构,如部门、知识点等,数据举例:物电学院->自动化系->08自动化->08自动化一班,类似于这样的数据结构。如何查询某个节点下的所有子孙节点,在程序设计中有两种方法。以如下表结构做说明:
CREATE TABLE `basic_depart` (
`departId` char(32) CHARACTER SET latin1 NOT NULL COMMENT '部门ID',
`parentId` char(32) CHARACTER SET latin1 DEFAULT NULL COMMENT '父ID',
`nodeType` tinyint(4) DEFAULT NULL COMMENT '0:无下级;1:有下级',
`name` varchar(50) DEFAULT NULL COMMENT '部门名称',
`telephone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`sortOrder` tinyint(4) DEFAULT NULL COMMENT '排序',
`remark` varchar(1000) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`departId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1、写一个递归函数来递归查询
public List getDepartTree(String parentId) throws SQLException {
List departList = dao.queryList("depart.getChildDepart", parentId);
if (departList != null && departList.size() > 0) {
Iterator departIterator = departList.iterator();
while (departIterator.hasNext()) {
Depart depart = departIterator.next();
depart.setSubDepartList(getDepartTree(depart.getDepartId()));
}
}
return departList;
}
递归函数比较简单,记住递归的2个要素:递归结束的条件和递归公式,一般不会有啥问题。
2、参考网上一哥们的思路,写一个求出所有子孙节点ID和自己的ID的mysql function
DROP FUNCTION IF EXISTS oes.depart_getChilds;
CREATE FUNCTION oes.`depart_getChilds`(rootId varchar(32)) RETURNS varchar(10000) CHARSET latin1
BEGIN
DECLARE sTemp VARCHAR(10000);
DECLARE sTempChd VARCHAR(10000);
SET sTemp = rootId;
SET sTempChd = rootId;
WHILE sTempChd is not null DO
SELECT group_concat(departId) INTO sTempChd FROM basic_depart where FIND_IN_SET(parentId,sTempChd) > 0;
IF sTempChd is not null THEN
SET sTemp = concat(sTemp,',',sTempChd);
END IF;
END WHILE;
RETURN sTemp;
END;
然后在程序或者ibatis里面设置查询条件类似下面:
select * from basic_depart where find_in_set(departId,depart_getChilds('root'))
这里稍微说一下,我数据库的全套charset和collation都是UTF8。
1.depart_getChilds函数RETURNS varchar(10000) CHARSET latin1,为什么要给返回值定义为charset latin1呢,因为貌似find_in_set函数的2个参数要求是latin1编码的(更正:不是find_in_set函数的问题,是departId字段的collation的设置问题)。不如此,就会报incorrect mix of collation的错误,为这个,浪费了我一个下午的时间。
2.本来我想写一个通用的mysql函数,处理所有树型结构的table,把table的名称,parentId字段名称,Id字段名称通通作为参数传递给getChilds函数。结果天不遂人愿,mysql不支持在function和procedure里面使用动态sql,要命3000。
3.为什么要第2个方法呢,一个是简单,一个是因为做ibatis的sqlmap的时候用得上,比如: