unicloud利用JQL查询树形数据gettree

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条件会对所有查询的节点生效

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注