树形数据结构是一类重要的非线性数据结构。诸如vue-router的数据、windows的文件目录都是树形数据结构,在业务中也常涉及到类似的树形数据,下面以课程为例进行设计:
- 课程:课程id、课程名
- 课程有子课程,例如后端课程包含java、springboot、php、Cloud
为课程增加字段来标记其父课程:
- 课程:课程id、课程名、父课程id
可得如下Course表:
| id | name | pid |
|---|---|---|
| 1 | 后端课 | 0 |
| 2 | 前端课 | 0 |
| 3 | java | 1 |
| 4 | springboot | 1 |
| 5 | vue | 2 |
| 6 | javascript | 2 |
依据上表,可构造出如下数据:即扁平数据转树形数据
{
course: [
{
id: 1,
name: '后端课',
pid: 0,
children: [
{
id: 3,
pid: 1,
name: 'java'
},
{
id: 4,
pid: 1,
name: 'springboot'
}
]
},
{
id: 2,
name: '前端课',
pid: 0,
children: [
{
id: 5,
pid: 2,
name: 'vue'
},
{
id: 6,
pid: 2,
name: 'javascript'
}
]
}
]
}
如果构造上述数据结构?
方案一: 两次查询, 对数据进行组装处理:
let parentList = 查询到所有pid为0的项
let childList = 查询到所有pid不为0的项
parentList.map((item) => {
item['children'] = [] // 构造子项
childList.map((subItem) => {
if (subItem.pid === item.id) {
return item.children.push(subItem)
}
})
})
return parentList
方案二: 一次查询所有项, 进行扁平数组转Tree
let courseData = 所有项
return arrayToTree(courseData, 'id', 'pid', 'children')
/**
* 将扁平数组结构转为Tree
* @param {Array} items 要转换的数组
* @param {String} idField 数据id
* @param {String} pidField 数据父id
* @param {String} childrenField 子项字段
* @returns {Tree}
*/
static arrayToTree(items, idField, pidField, childrenField) {
const result = []
const itemMap = {}
for (const item of items) {
const id = item[idField]
const pid = item[pidField]
if (!itemMap[id]) itemMap[id] = { [childrenField]: [] }
itemMap[id] = { ...item, [childrenField]: itemMap[id][childrenField] }
const treeItem = itemMap[id]
if (pid === 0) result.push(treeItem)
else {
if (!itemMap[pid]) itemMap[pid] = { [childrenField]: [] }
itemMap[pid][childrenField].push(treeItem)
}
}
return result
}
}
方案三: sql上进行优化, 即关联查询:
SELECT
`Course`.`id`,
`Course`.`name`,
`Course`.`pid`,
`subCourseList`.`id` AS `subCourseList.id`,
`subCourseList`.`name` AS `subCourseList.name`,
`subCourseList`.`pid` AS `subCourseList.pid`,
FROM
`course` AS `Course`
LEFT OUTER JOIN
`course` AS `subCourseList`
ON
`Course`.`id` = `subCourseList`.`pid`
WHERE `Course`.`pid` = 0 ;
注意: 数据量庞大时, 关联查询相比两次查询性能消耗更大, 因为关联查询要进行笛卡尔积, 一个不错的方案是第一次使用关联查询后将查询结果缓存到redis中, 之后从redis里读取查询结果。