HBuilderX 3.0.3+起,JQL支持在get方法内传入getTree参数查询树状结构数据。(HBuilderX 3.0.5+ unicloud-db组件开始支持,之前版本只能通过js方式使用)
树形数据,在数据库里一般不会按照tree的层次来存储,因为按tree结构通过json对象的方式存储不同层级的数据,不利于对tree上的某个节点单独做增删改查。
一般存储树形数据,tree上的每个节点都是一条单独的数据表记录,然后通过类似parent_id来表达父子关系。
如部门的数据表,里面有2条数据,一条数据记录是“总部”,parent_id
为空;另一条数据记录“一级部门A”,parent_id
为总部的_id
{
"_id": "5fe77207974b6900018c6c9c",
"name": "总部",
"parent_id": "",
"status": 0
}
{
"_id": "5fe77232974b6900018c6cb1",
"name": "一级部门A",
"parent_id": "5fe77207974b6900018c6c9c",
"status": 0
}
虽然存储格式是分条记录的,但查询反馈到前端的数据仍然需要是树形的。这种转换在过去比较复杂。
JQL提供了一种简单、优雅的方案,在DB Schema里配置parentKey来表达父子关系,然后查询时声明使用Tree查询,就可以直接查出树形数据。
department部门表的schema中,将字段parent_id
的”parentKey”设为”_id”,即指定了数据之间的父子关系,如下:
{
"bsonType": "object",
"required": ["name"],
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"name": {
"bsonType": "string",
"description": "名称"
},
"parent_id": {
"bsonType": "string",
"description": "父id",
"parentKey": "_id", // 指定父子关系为:如果数据库记录A的_id和数据库记录B的parent_id相等,则A是B的父级。
},
"status": {
"bsonType": "int",
"description": "部门状态,0-正常、1-禁用"
}
}
}
parentKey字段将数据表不同记录的父子关系描述了出来。查询就可以直接写了。
注意:一个表的一次查询中只能有一个父子关系。如果一个表的schema里多个字段均设为了parentKey,那么需要在JQL中通过parentKey()方法指定某个要使用的parentKey字段。
schema里描述好后,查询就变的特别简单。
查询树形数据,分为 查询所有子节点 和 查询父级路径 这2种需求。
查询所有子节点
指定符合条件的记录,然后查询它的所有子节点,并且可以指定层级,返回的结果是以符合条件的记录为一级节点的所有子节点数据,并以树形方式嵌套呈现。
只需要在JQL的get方法中增加getTree
参数,如下
// get方法示例
get({
getTree: {
limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15,最小1
startWith: "parent_code=='' || parent_code==null" // 第一层级条件,此初始条件可以省略,不传startWith时默认从最顶级开始查询
}
})
// 使用getTree时上述参数可以简写为以下写法
get({
getTree: true
})
完整的代码如下:
db.collection("department").get({
getTree: {}
})
.then((res) => {
const resdata = res.result.data
console.log("resdata", resdata);
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
}).finally(() => {
})
查询的结果如下:
"data": [{
"_id": "5fe77207974b6900018c6c9c",
"name": "总部",
"parent_id": "",
"status": 0,
"children": [{
"_id": "5fe77232974b6900018c6cb1",
"name": "一级部门A",
"parent_id": "5fe77207974b6900018c6c9c",
"status": 0,
"children": []
}]
}]
可以看出,每个子节点,被嵌套在父节点的”children”下,这个”children”是一个固定的格式。
如果不指定getTree的参数,会把department表的所有数据都查出来,从总部开始到10级部门,以树形结构提供给客户端。
如果有多个总部,即多行记录的parent_id
为空,则多个总部会分别作为一级节点,把它们下面的所有children一级一级拉出来。如下:
"data": [
{
"_id": "5fe77207974b6900018c6c9c",
"name": "总部",
"parent_id": "",
"status": 0,
"children": [{
"_id": "5fe77232974b6900018c6cb1",
"name": "一级部门A",
"parent_id": "5fe77207974b6900018c6c9c",
"status": 0,
"children": []
}]
},
{
"_id": "5fe778a10431ca0001c1e2f8",
"name": "总部2",
"parent_id": "",
"children": [{
"_id": "5fe778e064635100013efbc2",
"name": "总部2的一级部门B",
"parent_id": "5fe778a10431ca0001c1e2f8",
"children": []
}]
}
]
如果觉得返回的parent_id
字段多余,也可以指定.field("_id,name")
,过滤掉该字段。
getTree的参数limitLevel的说明
limitLevel表示查询返回的树的最大层级。超过设定层级的节点不会返回。
- limitLevel的默认值为10。
- limitLevel的合法值域为1-15之间(包含1、15)。如果数据实际层级超过15层,请分层懒加载查询。
- limitLevel为1,表示向下查一级子节点。假如数据库中有2级、3级部门,如果设limitLevel为1,且查询的是“总部”,那么返回数据包含“总部”和其下的一级部门。
getTree的参数startWith的说明
如果只需要查“总部”的子部门,不需要“总部2”,可以在startWith里指定(getTree: {"startWith":"name=='总部'"}
)。
使用中请注意startWith和where的区别。where用于描述对所有层级的生效的条件(包括第一层级)。而startWith用于描述从哪个或哪些节点开始查询树。
startWith不填时,默认的条件是 'parent_id==null||parent_id==""'
,即schema配置parentKey的字段为null(即不存在)或值为空字符串时,这样的节点被默认视为根节点。
假设上述部门表内有以下数据
{
"_id": "1",
"name": "总部",
"parent_id": "",
"status": 0
}
{
"_id": "11",
"name": "一级部门A",
"parent_id": "1",
"status": 0
}
{
"_id": "12",
"name": "一级部门B",
"parent_id": "1",
"status": 1
}
以下查询语句指定startWith为_id=="1"
、where条件为status==0
,查询总部下所有status为0的子节点。
db.collection("department")
.where('status==0')
.get({
getTree: {
startWith: '_id=="1"'
}
})
.then((res) => {
const resdata = res.result.data
console.log("resdata", resdata);
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
}).finally(() => {
})
查询的结果如下:
{
"data": [{
"_id": "1",
"name": "总部",
"parent_id": "",
"status": 0,
"children": [{
"_id": "11",
"name": "一级部门A",
"parent_id": "1",
"status": 0,
"children": []
}]
}]
}
需要注意的是where内的条件也会对第一级数据生效,例如将上面的查询改成如下写法
db.collection("department")
.where('status==1')
.get({
getTree: {
startWith: '_id=="1"'
}
})
.then((res) => {
const resdata = res.result.data
console.log("resdata", resdata);
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
}).finally(() => {
})
此时将无法查询到数据,返回结果如下
{
"data": []
}
通过parentKey方法指定某个parentKey
如果表的schema中有多个字段都配置了parentKey,但查询时其实只能有一个字段的parentKey关系可以生效,那么此时就需要通过parentKey()方法来指定这次查询需要的哪个parentKey关系生效。
parentKey()方法的参数是字段名。
db.collection('department')
.parentKey('parent_id') // 如果表schema只有一个字段设了parentKey,其实不需要指定。有多个字段被设parentKey才需要用这个方法指定
.get({
getTree: true
})
示例
插件市场有一个 家谱 的示例,可以参阅:https://ext.dcloud.net.cn/plugin?id=3798
大数据量的树形数据查询
如果tree的数据量较大,则不建议一次性把所有的树形数据返回给客户端。建议分层查询,即懒加载。
比如地区选择的场景,全国的省市区数据量很大,一次性查询所有数据返回给客户端非常耗时和耗流量。可以先查省,然后根据选择的省再查市,以此类推。
注意
- 暂不支持使用getTree的同时使用联表查询
- 如果使用了where条件会对所有查询的节点生效
- 如果使用了limit设置最大返回数量仅对根节点生效
查询树形结构父节点路径
getTree是查询子节点,而getTreePath,则是查询父节点。
get方法内传入getTreePath参数对包含父子关系的表查询返回树状结构数据某节点路径。
// get方法示例
get({
getTreePath: {
limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15,最小1
startWith: 'name=="一级部门A"' // 末级节点的条件,此初始条件不可以省略
}
})
查询返回的结果为,从“一级部门A”起向上找10级,找到最终节点后,以该节点为根,向下嵌套children,一直到达“一级部门A”。
返回结果只包括“一级部门A”的直系父,其父节点的兄弟节点不会返回。所以每一层数据均只有一个节点。
仍以上面department的表结构和数据为例
db.collection("department").get({
getTreePath: {
"startWith": "_id=='5fe77232974b6900018c6cb1'"
}
})
.then((res) => {
const treepath = res.result.data
console.log("treepath", treepath);
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
}).finally(() => {
uni.hideLoading()
// console.log("finally")
})
查询返回结果
从根节点“总部”开始,返回到“一级部门A”。“总部2”等节点不会返回。
{
"data": [{
"_id": "5fe77207974b6900018c6c9c",
"name": "总部",
"parent_id": "",
"children": [{
"_id": "5fe77232974b6900018c6cb1",
"name": "一级部门A",
"parent_id": "5fe77207974b6900018c6c9c"
}]
}]
}
如果startWith指定的节点没有父节点,则返回自身。
如果startWith匹配的节点不止一个,则以数组的方式,返回每个节点的treepath。
例如“总部”和“总部2”下面都有一个部门的名称叫“销售部”,且"startWith": "name=='销售部'"
,则会返回“总部”和“总部2”两条treepath,如下
{
"data": [{
"_id": "5fe77207974b6900018c6c9c",
"name": "总部",
"parent_id": "",
"children": [{
"_id": "5fe77232974b6900018c6cb1",
"name": "销售部",
"parent_id": "5fe77207974b6900018c6c9c"
}]
}, {
"_id": "5fe778a10431ca0001c1e2f8",
"name": "总部2",
"parent_id": "",
"children": [{
"_id": "5fe79fea23976b0001508a46",
"name": "销售部",
"parent_id": "5fe778a10431ca0001c1e2f8"
}]
}]
}
注意
- 暂不支持使用getTreePath的同时使用其他联表查询语法
- 如果使用了where条件会对所有查询的节点生效