JQL联表查询策略调整
clientDB将于2021-04-28
日上午10点,调整联表查询策略。在此时间点后如果不改动服务空间,不会有变化。如果改动了schema、uni-id模块,则会自动升级clientDB到新策略。
调整前
- 以field方法为联表查询依据,field内存在
{}
时才会进行联表查询 - collection中写3个表,第3个表可与第2个表关联查询(其实本身是错误写法,但之前版本未限制这种做法)
调整后
- field方法仅用于字段过滤,只要collection方法内有多个表名,就会自动联表查询,无需在field中编写
{}
。(详见下方说明) - collection中写多个表名时,只要第一个表是主表,剩余表均与主表做关联查询。第3个表只能与主表联查,不能与第2个表联查。嵌套联表查询的错误用法不再予以支持,后续会提供其他写法对嵌套联表查询进行支持
调整后clientDB会自动从schema读取所有collection方法内出现的表的关联关系进行联表查询。
当多表之间存在多个foreignKey关系时,之前通过field字段指定要使用哪些foreignKey。从3.1.10版本起,clientDB新增了foreignKey方法,可通过白名单方式手动指定使用的关联关系。
类似于field白名单策略,不写就代表全要,写了就只要指定的。
foreignKey方法的参数格式是字符串。
例如:
db.collection('comment,uni-id-users')
.foreignKey('comment.sender,comment.receiver')
.get()
上述代码表示联查时仅使用comment表内的sender字段和receiver字段对应的foreignKey,忽略其他foreignKey关系。
clientDB已发布过渡版本3.1.10,支持foreignKey方法以方便开发者编写兼容代码,过渡版本是向下兼容的,但对即将废弃的用途做了告警。
开发者若想知道自己的联查代码是否涉及将被废弃的写法,推荐尽快更新到HBuilderX 3.1.10
版本,连接本地云函数环境运行测试,触发clientDB请求,即将废弃的写法产生的请求会在控制台打印如下信息:[System Info]clientDB会在2021年4月26日进行升级,此写法不再兼容,如何调整请参考:https://ask.dcloud.net.cn/article/38966
在截止日期后,会自动移除对废弃写法的支持。
如果连接云端云函数测试clientDB兼容性,需要上传一次schema触发云端clientDB更新到3.1.10+版本
下面以附录内的三个表为例,讲解联表查询具体调整,(_id在mongoDB内有特殊的表现,下面三个表内均不以_id举例),下方所有示例代码schema及数据可在本贴附件内找到
限制错误写法
嵌套联表查询
调整前clientDB虽未暴露嵌套联查用法,但是也没有做出限制,导致有部分开发者错误的使用了下方示例的写法进行嵌套联表查询。调整后仅支持第一个表作为主表,关联其他表进行查询,不可在副表之间再进行关联,即以下写法不再支持(后续会提供其他写法来进行此类查询)
// 错误写法
db.collection('comment,article,user')
.field('content,article{author{name}}')
.get()
将某字段使用as重命名为表内存在的字段
// 错误写法
db.collection('comment')
.field('content as comment_id') // 不支持将content重命名为comment_id因为comment_id也是comment表的字段
.get()
联表查询副表字段过滤新写法
调整前如果要对副表数据进行过滤需要在field方法的花括号内进行过滤,如以下写法对副表user进行字段过滤,仅获取user表的name
调整后应将主表副表合并查询后的结果看作一个表,直接通过.
进行副表字段的筛选。旧写法仍支持
// 调整前写法
db.collection('article,user')
.where('article_id=="1"')
.field('title,content,author{name}')
.get()
// 调整后写法
db.collection('article,user')
.where('article_id=="1"')
.field('title,content,author.name')
.get()
注意此写法副表别名的表现,见下方示例
// 调整后写法
db.collection('article,user')
.where('article_id=="1"')
.field('title,content,author.name as author_name')
.get()
返回结果
[{
"title": "title1",
"content": "content1",
"author_name": ["user1"] // 联表后author是一个数组,此处表现为将author下每一项的name提取出来作为一个数组
}]
自动进行数据表的关联
调整前clientDB会根据传入的field对collection内的表进行关联,即仅使用field内{}
明确表示的关联关系。调整后,clientDB会自动补足主表和副表之间的关联关系,即使field内不存在{}
此调整可能会导致原有联表查询访问调整前并未访问的字段,如果此字段恰好无权访问会导致查询报错
例1:
db.collection('comment,article')
.where('comment_id=="1-1"')
.field('content,article')
.get()
由于collection内包含两个集合,上述代码在调整后会自动进行联表查询(使用所有的关联关系),查询到的结果如下
// 调整后结果
[{
"content": "comment1-1",
"article": [{
"article_id": "1",
"title": "title1",
"content": "content1",
"author": "1"
}]
}]
// 调整前结果
[{
"content": "comment1-1",
"article": "1"
}]
例1在调整前下不会进行联表查询,如果希望维持旧的表现只需要去除collection方法内的article表即可
db.collection('comment')
.where('comment_id=="1-1"')
.field('content,article')
.get()
例2:
db.collection('comment,user')
.where('comment_id=="1-1"')
.field('content,sender,receiver{name}')
.get()
comment表内sender和receiver均存在foreignKey指向user表,调整前上述写法不会使用sender对应的foreignKey进行联表查询,调整后sender对应的foreignKey也会进行一次联表查询,如果仅希望receiver字段对应的foreignKey生效可以使用如下写法
// 例2调整为此写法能同时兼容新旧用法
db.collection('comment,user')
.where('comment_id=="1-1"')
.field('content,sender,receiver.name') // 去除原查询中的花括号
.foreignKey('comment.receiver') // 仅使用comment表内receiver字段下的foreignKey
.get()
<unicloud-db>
组件调整也和clientDB api类似,新增了一个foreignKey
属性传值同上
<unicloud-db collection="comment,user" where="comment_id=='1-1'" field="content,sender,receiver{name}" foreignKey="comment.receiver"></unicloud-db>
例3:
存在不止两个表的联表查询也是一样会自动补足所有副表和主表的关系(注意仅在所有副表和主表之间进行关联查询,副表之间即使存在关联关系也无法进行关联查询)
db.collection('comment,article,user')
.where('comment_id=="1-1"')
.get()
返回结果如下
[{
"comment_id": "1-1",
"content": "comment1-1",
"article": [{
"article_id": "1",
"title": "title1",
"content": "content1",
"author": "1"
}],
"sender": [{
"uid": "1",
"name": "user1"
}],
"receiver": [{
"uid": "2",
"name": "user2"
}]
}]
副表foreignKey联查
之前的clientDB版本,只支持主表的foreignKey,把副本内容嵌入主表的foreignKey字段下面。不支持处理副本的foreignKey。(如果你觉得能用,其实是bug,查出来的数是乱的,别依赖这种写法)
调整后,新版将正式支持副表foreignKey联查。将把副本的数据以数组的方式嵌入到主表中。
例:
db.collection('article,comment')
.where('article_id=="1"')
.field('content,article_id')
.get()
查询结果如下:
[{
"content": "content1",
"article_id": {
"comment": [{ // 逆向foreignKey查询时此处会自动插入一层副表表名
"comment_id": "1-1",
"content": "comment1-1",
"article": "1",
"sender": "1",
"receiver": "2"
},
{
"comment_id": "1-2",
"content": "comment1-2",
"article": "1",
"sender": "2",
"receiver": "1"
}]
}
}]
如需对上述查询的副表字段进行过滤,需要注意多插入的一层副表表名
// 过滤副表字段
db.collection('article,comment')
.where('article_id=="1"')
.field('content,article_id{comment{content}}')
.get()
// 查询结果如下
[{
"content": "content1",
"article_id": {
"comment": [{ // 使用副本foreignKey联查时此处会自动插入一层副表表名
"content": "comment1-1"
},
{
"content": "comment1-2"
}]
}
}]
附录
表结构及数据
// user - 用户表
// schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": false,
"update": false,
"delete": false
},
"properties": {
"uid": {
"bsonType": "string"
},
"name": {
"bsonType": "string"
}
}
}
// data
{
"uid": "1",
"name": "user1"
}
{
"uid": "2",
"name": "user2"
}
// article - 文章表
// schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": false,
"update": false,
"delete": false
},
"properties": {
"article_id": {
"bsonType": "string"
},
"title": {
"bsonType": "string"
},
"content": {
"bsonType": "string"
},
"author": {
"bsonType": "string",
"foreignKey": "user.uid"
}
}
}
// data
{
"article_id": "1",
"title": "title1",
"content": "content1",
"author": "1"
}
{
"article_id": "2",
"title": "title2",
"content": "content2",
"author": "1"
}
{
"article_id": "3",
"title": "title3",
"content": "content3",
"author": "2"
}
// comment - 评论表
// schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": false,
"update": false,
"delete": false
},
"properties": {
"comment_id": {
"bsonType": "string"
},
"content": {
"bsonType": "string"
},
"article": {
"bsonType": "string",
"foreignKey": "article.article_id"
},
"sender": {
"bsonType": "string",
"foreignKey": "user.uid"
},
"receiver": {
"bsonType": "string",
"foreignKey": "user.uid"
}
}
}
// data
{
"comment_id": "1-1",
"content": "comment1-1",
"article": "1",
"sender": "1",
"receiver": "2"
}
{
"comment_id": "1-2",
"content": "comment1-2",
"article": "1",
"sender": "2",
"receiver": "1"
}
{
"comment_id": "2-1",
"content": "comment2-1",
"article": "2",
"sender": "1",
"receiver": "2"
}
{
"comment_id": "2-2",
"content": "comment2-2",
"article": "2",
"sender": "2",
"receiver": "1"
}