查询树形数据gettree
数据存储:tree上的每个节点都是一条单独的数据表记录,然后通过类似parent_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-禁用"
}
}
}
定义json数据
{
"_id": "5fe77207974b6900018c6c9c",
"name": "总部",
"parent_id": "",
"status": 0
}
{
"_id": "5fe77232974b6900018c6cb1",
"name": "一级部门A",
"parent_id": "5fe77207974b6900018c6c9c",
"status": 0
}
注意:一个表的一次查询中只能有一个父子关系。如果一个表的schema里多个字段均设为了parentKey,那么需要在JQL中通过parentKey()方法指定某个要使用的parentKey字段。
查询所有的节点
get({
getTree: {
limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15,最小1
startWith: "parent_id=='' || parent_id==null" // 第一层级条件,此初始条件可以省略,不传startWith时默认从最顶级开始查询
}
})
limitLevel
表示查询返回的树的最大层级。超过设定层级的节点不会返回。如果数据实际层级超过15层,请分层懒加载查询。
startWith
用于描述从哪个或哪些节点开始查询树。不填时,默认的条件是 'parent_id==null||parent_id==""'
完整代码
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": []
}]
}]
结合where语句
注意的是where内的条件也会对第一级数据生效
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(() => {
})
查询树形结构父节点路径
get({
getTreePath: {
limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15,最小1
startWith: 'name=="一级部门A"' // 末级节点的条件,此初始条件不可以省略
}
})
返回结果只包括“一级部门A”的直系父,其父节点的兄弟节点不会返回。所以每一层数据均只有一个节点。
注意
- 暂不支持使用getTreePath的同时使用其他联表查询语法
- 如果使用了where条件会对所有查询的节点生效
分组统计groupby
数据分组统计,即根据某个字段进行分组(groupBy),然后对其他字段分组后的值进行求和、求数量、求均值。
JQL的groupField
里不能直接写field字段,只能使用分组运算方法来处理字段,常见的累积器计算符包括:count(*)、sum(字段名称)、avg(字段名称)。
开发者也不应该在groupBy和groupField里使用_id字段,_id是唯一的,没有统一意义。
示例: 如果数据库score表为某次比赛统计的分数数据,每条记录为一个学生的分数。学生有所在的年级(grade)、班级(class)、姓名(name)、分数(score)等字段属性。
{
_id: "1",
grade: "1",
class: "A",
name: "zhao",
score: 5
}
{
_id: "2",
grade: "1",
class: "A",
name: "qian",
score: 15
}
{
_id: "3",
grade: "1",
class: "B",
name: "li",
score: 15
}
{
_id: "4",
grade: "1",
class: "B",
name: "zhou",
score: 25
}
{
_id: "5",
grade: "2",
class: "A",
name: "wu",
score: 25
}
{
_id: "6",
grade: "2",
class: "A",
name: "zheng",
score: 35
}
求和
const res = await db.collection('score')
.groupBy('grade,class')
.groupField('sum(score) as totalScore')
.get()
求平均值
const res = await db.collection('score')
.groupBy('grade,class')
.groupField('avg(score) as avgScore')
.get()
统计数量
const res = await db.collection('score')
.groupBy('grade,class')
.groupField('count(*) as totalStudents')
.get()
注意:count(*)为固定写法,括号里的*可以省略
按日分组
假设要统计uni-id-users表的每日新增注册用户数量。表内有以下数据:
{"_id":"1","username":"name1","register_date":1611367810000// 2021-01-23 10:10:10}{"_id":"2","username":"name2","register_date":1611367810000// 2021-01-23 10:10:10}{"_id":"3","username":"name3","register_date":1611367810000// 2021-01-23 10:10:10}{"_id":"4","username":"name4","register_date":1611281410000// 2021-01-22 10:10:10}{"_id":"5","username":"name5","register_date":1611281410000// 2021-01-22 10:10:10}{"_id":"6","username":"name6","register_date":1611195010000// 2021-01-21 10:10:10}
const res = await db.collection('uni-id-users')
.groupBy('dateToString(add(new Date(0),register_date),"%Y-%m-%d","+0800") as date')
.groupField('count(*) as newusercount')
.get()
其中:
add操作符的用法为add(值1,值2)。add(new Date(0),register_date)
表示给字段register_date + 0,这个运算没有改变具体的时间,但把register_date的格式从时间戳转为了日期类型。
dateToString操作符的用法为dateToString(日期对象,格式化字符串,时区)
查询返回结果
res = {
result: {
data: [{
date: '2021-01-23',
newusercount: 3
},{
date: '2021-01-22',
newusercount: 2
},{
date: '2021-01-21',
newusercount: 1
}]
}
}
同时发送多条数据库请求
使用multiSend可以将多个数据库请求合并成一个发送。
const bannerQuery = db.collection('banner').field('url,image').getTemp() // 这里使用getTemp不直接发送get请求,等到multiSend时再发送
const noticeQuery = db.collection('notice').field('text,url,level').getTemp()
const res = await db.multiSend(bannerQuery,noticeQuery)
返回值
// 上述请求返回以下结构
res = {
code: 0, // 请求整体执行错误码,注意如果多条查询执行失败,这里的code依然是0,只有出现网络错误等问题时这里才会出现错误
message: '', // 错误信息
dataList: [{
code: 0, // bannerQuery 对应的错误码
message: '', // bannerQuery 对应的错误信息
data: [] // bannerQuery 查询到的数据
}, {
code: 0, // noticeQuery 对应的错误码
message: '', // noticeQuery 对应的错误信息
data: [] // noticeQuery 查询到的数据
}]
}
action
从HBuilderX 3.6.11开始,推荐使用数据库触发器替代action云函数。以下内容仅为向下兼容保留
action的作用是在执行前端发起的数据库操作时,额外触发一段云函数逻辑。它是一个可选模块。action是运行于云函数内的,可以使用云函数内的所有接口。
action支持一次使用多个,比如使用db.action("action-a,action-b"),其执行流程为action-a.before->action-b.before->执行数据库操作->action-b.after->action-a.after。在任一before环节抛出错误直接进入after流程,在after流程内抛出的错误会被传到下一个after流程。
action是一种特殊的云函数,它不占用服务空间的云函数数量。
注意action方法是db对象的方法,只能跟在db后面,不能跟在collection()后面
db.action("someactionname").collection('table1')
action创建
项目-->uniCloud-->cloudfunctions-->uni-clientDB-actions(如果没有该文件夹,右键cloudfunctions创建该文件夹)--> 右键新建action
使用
// 客户端发起请求,给todo表新增一行数据,同时指定action为add-todo
const db = uniCloud.database()
db.action('add-todo') //注意action方法是db的方法,只能跟在db后面,不能跟在collection()后面
.collection('todo')
.add({
title: 'todo title'
})
.then(res => {
console.log(res)
}).catch(err => {
console.error(err)
})
定义add-todo action
// 一个action文件示例 uni-clientDB-actions/add-todo.js
module.exports = {
// 在数据库操作之前执行
before: async(state,event)=>{
// state为当前数据库操作状态其格式见下方说明
// event为传入云函数的event对象
// before内可以操作state上的newData对象对数据进行修改,比如:
state.newData.create_time = Date.now()
// 指定插入或修改的数据内的create_time为Date.now()
// 执行了此操作之后实际插入的数据会变成 {title: 'todo title', create_time: xxxx}
// 实际上,这个场景,有更简单的实现方案:在db schema内配置defaultValue或者forceDefaultValue,即可自动处理新增记录使用当前服务器时间
},
// 在数据库操作之后执行
after:async (state,event,error,result)=>{
// state为当前数据库操作状态其格式见下方说明
// event为传入云函数的event对象
// error为执行操作的错误对象,如果没有错误error的值为null
// result为执行command返回的结果
if(error) {
throw error
}
// after内可以对result进行额外处理并返回
result.msg = 'hello'
return result
}
}
state参数说明
// state参数格式如下
{
command: {
// getMethod('where') 获取所有的where方法,返回结果为[{$method:'where',$param: [{a:1}]}]
getMethod,
// getParam({name:'where',index: 0}) 获取第1个where方法的参数,结果为数组形式,例:[{a:1}]
getParam,
// setParam({name:'where',index: 0, param: [{a:1}]}) 设置第1个where方法的参数,调用之后where方法实际形式为:where({a:1})
setParam
},
auth: {
uid, // 用户ID,如果未获取或者获取失败uid值为null
role, // 通过uni-id获取的用户角色,需要使用1.1.9以上版本的uni-id,如果未获取或者获取失败role值为[]
permission // 通过uni-id获取的用户权限,需要使用1.1.9以上版本的uni-id,如果未获取或者获取失败permission值为[],注意登录时传入needPermission才可以获取permission,请参考 https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=rbac
},
// 事务对象,如果需要用到事务可以在action的before内使用state.transaction = await db.startTransaction()传入
transaction,
// 更新或新增的数据
newData,
// 访问的集合
collection,
// 操作类型,可能的值'read'、'create'、'update'、'delete'
type
}
JQL Cache Redis
适用场景
需要频繁访问,但不会频繁修改的数据。比如更新不频繁的首页列表、banner、热搜、top排行等公共数据。
这些查询请求频次高,如果每次都去MongoDB读取,又慢又贵,还可能超时或超并发。
配置方法
在uniCloud/cloudfunction/common/uni-config-center下创建uni-jql-cache-redis.json
文件
uni-jql-cache-redis.json文件是一个数组,其中每一项是一个缓存配置。示例内容如下
[{
"id": "test-get", // 缓存id,不可重复,必填
"jql": "db.collection('test').limit(10).get()", // 要缓存的数据库指令,必填
"expiresIn": 3600 // 缓存有效期,单位秒,非必填 如果不填,就意味着不会根据时间失效
}]
由于需要将查询语句放在json文件内,而json内字符串又需要引号包围,所以对于查询语句内使用双引号的场景需转义后再放到配置内。
// jql
db.collection('book').where('author=="caoxueqin"').get()
// 转义后
"db.collection('book').where('author==\"caoxueqin\"').get()"
联表查询
const book = db.collection('book').limit(10).getTemp()
const author = db.collection('author').getTemp()
db.collection(book, author).get()
uni-jql-cache-redis.json缓存配置如下
[{
"id": "book-author-lookup",
"jql": "const book = db.collection('book').limit(10).getTemp();const author = db.collection('author').getTemp();db.collection(book, author).get()",
"expiresIn": 3600
}]
删除redis缓存
数据变更时,应该主动删除Redis中的缓存。
const redis = uniCloud.redis()
const id = 'banner' // 这个id就是uni-jql-cache-redis.json
中配置的id
const cacheKey = unicloud:jql-cache:${id}:string
await redis.del(cacheKey)
注意事项
- 仅可缓存查询的JQL指令,增删改JQL指令无效
- 不可缓存使用了db.getCloudEnv()或$cloudEnv_开头的云端环境变量的查询
- 不可缓存使用了action的查询
- 启用JQL扩展库的云函数/云对象同时也要启用Redis扩展库才可以正常使用缓存功能,否则依然从数据库查询
- 配置后或设置缓存失效后,开发者可自己请求一次JQL查询,让cache生效。避免用户第一次访问时仍然访问MongoDB
在查询语句内如果有字符串使用引号,请务必保证单双引号和配置的缓存一致