diff --git a/docs/uniCloud/cf-database.md b/docs/uniCloud/cf-database.md index 96bbf091041a07ce9b5320e1290411309ee7376e..22f7ed97e00fc8c991deff367575eb37e563aa63 100644 --- a/docs/uniCloud/cf-database.md +++ b/docs/uniCloud/cf-database.md @@ -23,20 +23,26 @@ const db = uniCloud.database(); ``` - - + ## 获取集合的引用 ```js @@ -159,7 +165,7 @@ const collection = db.collection('user'); **阿里云暂不支持地理位置类型** - +参考:[GEO地理位置](#GEO地理位置) ### Null @@ -711,7 +717,9 @@ collection.where({name: dbCmd.eq('hey')}).update({ ``` -### 更新指令 +### 更新操作符 + +更多数据库操作符请查看[数据库操作符](#dbcmd) #### set @@ -981,10 +989,17 @@ db.collection('comments').doc('comment-id').update({ } ``` - + - +## 事务 + +**目前仅腾讯云支持事务,阿里云暂不支持** + +事务通常用来在某个数据库操作失败之后进行回滚。 + +### runTransaction + +发起事务。与`startTransaction`作用类似,接收参数类型不同 + +**`runTransaction` 的形式如下:** + +```javascript +db.runTransaction(callback: function, times: number) +``` + +**参数** + +|参数 |类型 |说明 | +|--- |--- |--- | +|callback |Function |事务执行函数,需为 async 异步函数或返回 Promise 的函数 | +|times |Number |事务执行最多次数,默认 3 次,成功后不重复执行,只有事务冲突时会重试,其他异常时不会重试| + +**返回值** + +`runTransaction`返回一个`Promise`,此`Promise.resolve`的结果为`callback`事务执行函数的返回值,`reject` 的结果为事务执行过程中抛出的异常或者是 `transaction.rollback` 传入的值 + +**callback 事务执行函数的说明** + +事务执行函数由开发者传入,函数接收一个参数 transaction,其上提供 collection 方法和 rollback 方法。collection 方法用于取数据库集合记录引用进行操作,rollback 方法用于在不想继续执行事务时终止并回滚事务。 + +事务执行函数必须为 `async` 异步函数或返回 `Promise` 的函数,当事务执行函数返回时,uniCloud 会认为用户逻辑已完成,自动提交(`commit`)事务,因此务必确保用户事务逻辑完成后才在 `async` 异步函数中返回或 `resolve Promise`。 + +事务执行函数可能会被执行多次,在内部发现事务冲突时会自动重复执行,如果超过设定的执行次数上限,会报错退出。 + +在事务执行函数中发生的错误,都会认为事务执行失败而抛错。 + +事务执行函数返回的值会作为 `runTransaction` 返回的 `Promise resolve` 的值,在函数中抛出的异常会作为 `runTransaction` 返回的 `Promise reject` 的值,如果事务执行函数中调用了 `transaction.rollback`,则传入 `rollback` 函数的值会作为 `runTransaction` 返回的 `Promise reject` 的值。 + +**限制** + +事务操作时为保障效率和并发性,只允许进行单记录操作,不允许进行批量操作,但可以在一个事务进行多次数据库操作。 + +**注意事项** + +开发者提供的事务执行函数正常返回时,uniCloud 会自动提交(`commit`)事务,请勿在事务执行函数内调用 `transaction.commit` 方法,该方法仅在通过 `db.startTransaction` 进行事务操作时使用 + +**示例代码** + +两个账户之间进行转账的简易示例 + +```javascript +const db = uniCloud.database() +const _ = db.command +exports.main = async (event) => { + try { + const result = await db.runTransaction(async transaction => { + const aaaRes = await transaction.collection('account').doc('aaa').get() + const bbbRes = await transaction.collection('account').doc('bbb').get() + + if (aaaRes.data && bbbRes.data) { + const updateAAARes = await transaction.collection('account').doc('aaa').update({ + data: { + amount: _.inc(-10) + } + }) + + const updateBBBRes = await transaction.collection('account').doc('bbb').update({ + data: { + amount: _.inc(10) + } + }) + + console.log(`transaction succeeded`) + + // 会作为 runTransaction resolve 的结果返回 + return { + aaaAccount: aaaRes.data.amount - 10, + } + } else { + // 会作为 runTransaction reject 的结果出去 + await transaction.rollback(-100) + } + }) + + return { + success: true, + aaaAccount: result.aaaAccount, + } + } catch (e) { + console.error(`transaction error`, e) + + return { + success: false, + error: e + } + } +} +``` + +### startTransaction + +发起事务。与`runTransaction`作用类似,接收参数类型不同 + +**`startTransaction` 形式如下** + +```javascript +// 与runTransaction不同,startTransaction不接收参数 +db.startTransaction() +``` + +**返回值** + +返回一个`Promise`,此`Promise resolve`的结果为事务操作对象(**注意这里与runTransaction的区别**),其上可通过 `collection API` 操作数据库,通过 `commit`(**使用`startTransaction`需要主动`commit`**) 或 `rollback` 来结束或终止事务。 + +**限制** + +事务操作时为保障效率和并发性,只允许进行单记录操作,不允许进行批量操作,但可以在一个事务进行多次数据库操作。 + +**示例代码** + +两个账户之间进行转账的简易示例 + +```javascript +const db = uniCloud.database() +const _ = db.command + +exports.main = async (event) => { + try { + const transaction = await db.startTransaction() + + const aaaRes = await transaction.collection('account').doc('aaa').get() + const bbbRes = await transaction.collection('account').doc('bbb').get() + + if (aaaRes.data && bbbRes.data) { + const updateAAARes = await transaction.collection('account').doc('aaa').update({ + data: { + amount: _.inc(-10) + } + }) + + const updateBBBRes = await transaction.collection('account').doc('bbb').update({ + data: { + amount: _.inc(10) + } + }) + + await transaction.commit() + + console.log(`transaction succeeded`) + + return { + success: true, + aaaAccount: aaaRes.data.amount - 10, + } + } else { + await transaction.rollback() + + return { + success: false, + error: `rollback`, + rollbackCode: -100, + } + } + } catch (e) { + console.error(`transaction error`, e) + + return { + success: false, + error: e + } + } +} +``` ## 聚合操作 @@ -1665,7 +1848,7 @@ db.collection('items').aggregate() } ``` - +``` ### group @@ -3179,6 +3362,1303 @@ db.collection('books').aggregate() .catch(err => console.error(err)) ``` + +## 数据库操作符 + +### 查询·逻辑操作符 + +#### and + +查询操作符,用于表示逻辑 "与" 的关系,表示需同时满足多个查询筛选条件 + + +##### 使用说明 + `and` 有两种使用情况: + + + +**1. 用在根查询条件** + + 此时需传入多个查询条件,表示需同时满足提供的多个完整查询条件 + + +``` +const dbCmd = db.command +db.collection('todo').where(dbCmd.and([ + { + progress: dbCmd.gt(50) + }, + { + tags: 'cloud' + } +])).get() +``` +但以上用 `and` 组成的查询条件是不必要的,因为传入的对象的各字段隐式组成了 “与” 的关系,上述条件等价于下方更简洁的写法: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.gt(50), + tags: 'cloud' +}).get() +``` +通常需要显示使用 `and` 是用在有跨字段或操作的时候,如以下表示 “progress 字段大于 50 或 tags 字段等于 cloud 或 tags 数组字段(如果 tags 是数组)中含有 cloud”: + + +``` +const dbCmd = db.command +db.collection('todo').where(dbCmd.and([ + dbCmd.or({ + progress: dbCmd.gt(50) + }), + dbCmd.or({ + tags: 'cloud' + }) +])).get() +``` + + +**2. 用在字段查询条件** + + 需传入多个查询操作符或常量,表示字段需满足或匹配给定的条件。 + + 如以下用前置写法的方式表示 "progress 字段值大于 50 且小于 100" + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.and(dbCmd.gt(50), dbCmd.lt(100)) +}) +``` +还可以用后置写法的方式表示同样的条件: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.gt(50).and(dbCmd.lt(100)) +}) +``` +注意 `Command` 默认也可以直接链式调用其他 `Command`,默认表示多个 `Command` 的与操作,因此上述代码还可以精简为: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.gt(50).lt(100) +}) +``` + +##### 调用风格 + 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 + + +``` +// 传入数组 +function and(expressions: Expression[]): Command +// 传入多参数 +function and(...expressions: Expression[]): Command +``` + +#### or + +查询操作符,用于表示逻辑 "或" 的关系,表示需同时满足多个查询筛选条件。或指令有两种用法,一是可以进行字段值的 “或” 操作,二是也可以进行跨字段的 “或” 操作。 + + +##### 字段值的或操作 + 字段值的 “或” 操作指的是指定一个字段值为多个值之一即可。 + + 如筛选出进度大于 80 或小于 20 的 todo: + + 流式写法: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.gt(80).or(dbCmd.lt(20)) +}) +``` +前置写法: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.or(dbCmd.gt(80), dbCmd.lt(20)) +}) +``` +前置写法也可接收一个数组: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.or([dbCmd.gt(80), dbCmd.lt(20)]) +}) +``` + +##### 跨字段的或操作 + 跨字段的 “或” 操作指条件 “或”,相当于可以传入多个 where 语句,满足其中一个即可。 + + 如筛选出进度大于 80 或已标为已完成的 todo: + + +``` +const dbCmd = db.command +db.collection('todo').where(dbCmd.or([ + { + progress: dbCmd.gt(80) + }, + { + done: true + } +])) +``` + +##### 调用风格 + 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 + + +``` +// 传入数组 +function or(expressions: Expression[]): Command +// 传入多参数 +function or(...expressions: Expression[]): Command +``` + +#### not + +查询操作符,用于表示逻辑 "非" 的关系,表示需不满足指定的条件。 + + +##### 示例 + 如筛选出进度不等于100的 todo: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.not(dbCmd.eq(100)) +}) +``` +`not` 也可搭配其他逻辑指令使用,包括 `and`, `or`, `nor`, `not`,如 `or`: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.not(dbCmd.or([dbCmd.lt(50), dbCmd.eq(100)])) +}) +``` + +#### nor + +查询操作符,用于表示逻辑 "都不" 的关系,表示需不满足指定的所有条件。如果记录中没有对应的字段,则默认满足条件。 + + +##### 示例 1 + 筛选出进度既不小于20又不大于80的 todo : + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.nor([dbCmd.lt(20), dbCmd.gt(80)]) +}) +``` +以上同时会筛选出不存在 `progress` 字段的记录,如果要要求 `progress` 字段存在,可以用 `exists` 指令: + + +``` +const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.exists().nor([dbCmd.lt(20), dbCmd.gt(80)]) + // 等价于以下非链式调用的写法: + // progress: dbCmd.exists().and(dbCmd.nor([dbCmd.lt(20), dbCmd.gt(80)])) +}).get() +``` + +##### 示例 2 + 筛选出 `progress` 不小于 20 且 `tags` 数组不包含 `miniprogram` 字符串的记录: + + +``` +const dbCmd = db.command +db.collection('todo').where(dbCmd.nor([{ + progress: dbCmd.lt(20), +}, { + tags: 'miniprogram', +}])).get() +``` +以上会筛选出满足以下条件之一的记录: + + +1. `progress` 不小于 20 且 `tags` 数组不包含 `miniprogram` 字符串 3. `progress` 不小于 20 且 `tags` 字段不存在 5. `progress` 字段不存在 且 `tags` 数组不包含 `miniprogram` 字符串 7. `progress` 不小于 20 且 `tags` 字段不存在 + 如果要求 `progress` 和 `tags` 字段存在,可以用 `exists` 指令: + + +``` +const dbCmd = db.command +db.collection('todo').where( + dbCmd.nor([{ + progress: dbCmd.lt(20), + }, { + tags: 'miniprogram', + }]) + .and({ + progress: dbCmd.exists(true), + tags: dbCmd.exists(true), + }) +) +``` + +##### 调用风格 + 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 + + +``` +// 传入数组 +function nor(expressions: Expression[]): Command +// 传入多参数 +function nor(...expressions: Expression[]): Command +``` + +### 查询·比较操作符 + +#### eq + +查询筛选条件,表示字段等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`, `Date`。 + + +##### 使用说明 + 比如筛选出所有自己发表的文章,除了用传对象的方式: + + +``` +const openID = 'xxx' +db.collection('articles').where({ + _openid: openID +}) +``` +还可以用指令: + + +``` +const dbCmd = db.command +const openID = 'xxx' +db.collection('articles').where({ + _openid: dbCmd.eq(openid) +}) +``` +注意 `eq` 指令比对象的方式有更大的灵活性,可以用于表示字段等于某个对象的情况,比如: + + +``` +// 这种写法表示匹配 stat.publishYear == 2018 且 stat.language == 'zh-CN' +db.collection('articles').where({ + stat: { + publishYear: 2018, + language: 'zh-CN' + } +}) +// 这种写法表示 stat 对象等于 { publishYear: 2018, language: 'zh-CN' } +const dbCmd = db.command +db.collection('articles').where({ + stat: dbCmd.eq({ + publishYear: 2018, + language: 'zh-CN' + }) +}) +``` + +#### neq + +查询筛选条件,表示字段不等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`, `Date`。 + + +##### 使用说明 + 表示字段不等于某个值,和 [eq](Command.eq.html) 相反 + +#### lt + +查询筛选操作符,表示需小于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +##### 示例代码 + 找出进度小于 50 的 todo + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.lt(50) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +#### lte + +查询筛选操作符,表示需小于或等于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +##### 示例代码 + 找出进度小于或等于 50 的 todo + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.lte(50) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +#### gt + +查询筛选操作符,表示需大于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +##### 示例代码 + 找出进度大于 50 的 todo + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.gt(50) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +#### gte + +查询筛选操作符,表示需大于或等于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +##### 示例代码 + 找出进度大于或等于 50 的 todo + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.gte(50) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +#### in + +查询筛选操作符,表示要求值在给定的数组内。 + + +##### 示例代码 + 找出进度为 0 或 100 的 todo + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.in([0, 100]) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +#### nin + +查询筛选操作符,表示要求值不在给定的数组内。 + + +##### 示例代码 + 找出进度不是 0 或 100 的 todo + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.nin([0, 100]) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +### 查询·字段操作符 + +#### exists + +判断字段是否存在 + + +##### 示例代码 + 找出存在 tags 字段的记录 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + tags: dbCmd.exists(true) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +#### mod + +查询筛选操作符,给定除数 divisor 和余数 remainder,要求字段作为被除数时 value % divisor = remainder。 + + +##### 示例代码 + 找出进度为 10 的倍数的字段的记录 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + progress: dbCmd.mod(10, 0) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +### 查询·数组操作符 + +#### all + +数组查询操作符。用于数组字段的查询筛选条件,要求数组字段中包含给定数组的所有元素。 + + +##### 示例代码 1:普通数组 + 找出 tags 数组字段同时包含 cloud 和 database 的记录 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + tags: dbCmd.all(['cloud', 'database']) +}) +.get({ + success: console.log, + fail: console.error +}) +``` + +##### 示例代码 2:对象数组 + 如果数组元素是对象,则可以用 `dbCmd.elemMatch` 匹配对象的部分字段 + + 假设有字段 `places` 定义如下: + + +``` +{ + "type": string + "area": number + "age": number +} +``` +找出数组字段中至少同时包含一个满足 “area 大于 100 且 age 小于 2” 的元素和一个满足 “type 为 mall 且 age 大于 5” 的元素 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + places: dbCmd.all([ + dbCmd.elemMatch({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }), + dbCmd.elemMatch({ + name: 'mall', + age: dbCmd.gt(5), + }), + ]), +}) +.get({ + success: console.log, + fail: console.error, +}) +``` + +#### elemMatch + +用于数组字段的查询筛选条件,要求数组中包含至少一个满足 `elemMatch` 给定的所有条件的元素 + + +##### 示例代码:数组是对象数组的情况 + 假设集合示例数据如下: + + +``` +{ + "_id": "a0", + "city": "x0", + "places": [{ + "type": "garden", + "area": 300, + "age": 1 + }, { + "type": "theatre", + "area": 50, + "age": 15 + }] +} +``` +找出 `places` 数组字段中至少同时包含一个满足 “area 大于 100 且 age 小于 2” 的元素 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + places: dbCmd.elemMatch({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }) +}) +.get() +``` +*注意**:如果不使用 `elemMatch` 而直接如下指定条件,则表示的是 `places` 数组字段中至少有一个元素的 `area` 字段大于 100 且 `places` 数组字段中至少有一个元素的 `age` 字段小于 2: + + +``` +const dbCmd = db.command +db.collection('todos').where({ + places: { + area: dbCmd.gt(100), + age: dbCmd.lt(2), + } +}) +.get() +``` + +##### 示例代码:数组元素都是普通数据类型的情况 + 假设集合示例数据如下: + + +``` +{ + "_id": "a0", + "scores": [60, 80, 90] +} +``` +找出 `scores` 数组字段中至少同时包含一个满足 “大于 80 且小于 100” 的元素 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + places: dbCmd.elemMatch(dbCmd.gt(80).lt(100)) +}) +.get() +``` + +#### size + +更新操作符,用于数组字段的查询筛选条件,要求数组长度为给定值 + + +##### 示例 + 找出 tags 数组字段长度为 2 的所有记录 + + +``` +const dbCmd = db.command +db.collection('todos').where({ + places: dbCmd.size(2) +}) +.get({ + success: console.log, + fail: console.error, +}) +``` + +### 查询·地理位置操作符 + +#### geoNear + +按从近到远的顺序,找出字段值在给定点的附近的记录。 + + +##### 索引要求 + 需对查询字段建立地理位置索引 + + +##### 示例代码 + 找出离给定位置 1 公里到 5 公里范围内的记录 + + +``` +const dbCmd = db.command +db.collection('restaurants').where({ + location: dbCmd.geoNear({ + geometry: db.Geo.Point(113.323809, 23.097732), + minDistance: 1000, + maxDistance: 5000, + }) +}).get() +``` + +#### geoWithin + +找出字段值在指定区域内的记录,无排序。指定的区域必须是多边形(Polygon)或多边形集合(MultiPolygon)。 + + +##### 索引要求 + 需对查询字段建立地理位置索引 + + +##### 示例代码 1:给定多边形 + +``` +const dbCmd = db.command +const { Point, LineString, Polygon } = db.Geo +db.collection('restaurants').where({ + location: dbCmd.geoWithin({ + geometry: Polygon([ + LineString([ + Point(0, 0), + Point(3, 2), + Point(2, 3), + Point(0, 0) + ]) + ]), + }) +}) +``` + +##### 示例代码 2:给定圆形 + 可以不用 `geometry` 而用 `centerSphere` 构建一个圆形。 + + `centerSphere` 对应的值的定义是:`[ [经度, 纬度], 半径 ]` + + 半径需以弧度计,比如需要 10km 的半径,则用距离除以地球半径 6378.1km 得出的数字。 + + +``` +const dbCmd = db.command +db.collection('restaurants').where({ + location: dbCmd.geoWithin({ + centerSphere: [ + [-88, 30], + 10 / 6378.1, + ] + }) +}) +``` + +#### geoIntersects + +找出给定的地理位置图形相交的记录 + + +##### 索引要求 + 需对查询字段建立地理位置索引 + + +##### 示例代码:找出和一个多边形相交的记录 + +``` +const dbCmd = db.command +const { Point, LineString, Polygon } = db.Geo +db.collection('restaurants').where({ + location: dbCmd.geoIntersects({ + geometry: Polygon([ + LineString([ + Point(0, 0), + Point(3, 2), + Point(2, 3), + Point(0, 0) + ]) + ]), + }) +}) +``` + +### 查询·表达式操作符 + +#### expr + +查询操作符,用于在查询语句中使用聚合表达式,方法接收一个参数,该参数必须为聚合表达式 + + +##### 使用说明 + +1. `expr` 可用于在聚合 [`match`](../aggregate/Aggregate.match.html) 流水线阶段中引入聚合表达式 3. 如果聚合 [`match`](../aggregate/Aggregate.match.html) 阶段是在 [`lookup`](../aggregate/Aggregate.lookup.html) 阶段内,此时的 `expr` 表达式内可使用 `lookup` 中使用 `let` 参数定义的变量,具体示例可见 [`lookup`](../aggregate/Aggregate.lookup.html) 的 `指定多个连接条件` 例子 5. `expr` 可用在普通查询语句(`where`)中引入聚合表达式 + +##### 示例代码 1:比较同一个记录中的两个字段 + 假设 `items` 集合的数据结构如下: + + +``` +{ + "_id": string, + "inStock": number, // 库存量 + "ordered": number // 被订量 +} +``` +找出被订量大于库存量的记录: + + +``` +const dbCmd = db.command +const $ = dbCmd.aggregate +db.collection('items').where(dbCmd.expr($.gt('$ordered', '$inStock'))).get() +``` + +##### 示例代码 2:与条件语句组合使用 + 假设 `items` 集合的数据结构如下: + + +``` +{ + "_id": string, + "price": number +} +``` +假设加个小于等于 10 的打 8 折,大于 10 的打 5 折,让数据库查询返回打折后价格小于等于 8 的记录: + + +``` +const dbCmd = db.command +const $ = dbCmd.aggregate +db.collection('items').where(dbCmd.expr( + $.lt( + $.cond({ + if: $.gte('$price', 10), + then: $.multiply(['$price', '0.5']), + else: $.multiply(['$price', '0.8']), + }) + , + 8 + ) +).get() +``` + +### 更新·字段操作符 + +#### set + +更新操作符,用于设定字段等于指定值。 + + +##### 使用说明 + 这种方法相比传入纯 JS 对象的好处是能够指定字段等于一个对象 + + +##### 示例 + +``` +// 以下方法只会更新 style.color 为 red,而不是将 style 更新为 { color: 'red' },即不影响 style 中的其他字段 +db.collection('todos').doc('doc-id').update({ + data: { + style: { + color: 'red' + } + } +}) + +// 以下方法更新 style 为 { color: 'red', size: 'large' } +db.collection('todos').doc('doc-id').update({ + data: { + style: dbCmd.set({ + color: 'red', + size: 'large' + }) + } +}) +``` + +#### remove + +更新操作符,用于表示删除某个字段。 + + +##### 示例代码 + 删除 style 字段: + + +``` +const dbCmd = db.command +db.collection('todos').doc('todo-id').update({ + data: { + style: dbCmd.remove() + } +}) +``` + +#### inc + +更新操作符,原子操作,用于指示字段自增 + + +##### 原子自增 + 多个用户同时写,对数据库来说都是将字段自增,不会有后来者覆写前者的情况 + + +##### 示例代码 + 将一个 todo 的进度自增 10: + + +``` +const dbCmd = db.command +db.collection('todos').doc('todo-id').update({ + data: { + progress: dbCmd.inc(10) + } +}) +``` + +#### mul + +更新操作符,原子操作,用于指示字段自乘某个值 + + +##### 原子自乘 + 多个用户同时写,对数据库来说都是将字段自乘,不会有后来者覆写前者的情况 + + +##### 示例代码 + 将一个 todo 的进度自乘 10: + + +``` +const dbCmd = db.command +db.collection('todos').doc('todo-id').update({ + data: { + progress: dbCmd.mul(10) + } +}) +``` + +#### min + +更新操作符,给定一个值,只有该值小于字段当前值才进行更新。 + + +##### 示例代码 + 如果字段 progress > 50,则更新到 50 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + progress: dbCmd.min(50) + } +}) +``` + +#### max + +更新操作符,给定一个值,只有该值大于字段当前值才进行更新。 + + +##### 示例代码 + 如果字段 progress < 50,则更新到 50 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + progress: dbCmd.max(50) + } +}) +``` + +#### rename + +更新操作符,字段重命名。如果需要对嵌套深层的字段做重命名,需要用点路径表示法。不能对嵌套在数组里的对象的字段进行重命名。 + + +##### 示例 1:重命名顶层字段 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + progress: dbCmd.rename('totalProgress') + } +}) +``` + +##### 示例 2:重命名嵌套字段 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + someObject: { + someField: dbCmd.rename('someObject.renamedField') + } + } +}) +``` +或: + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + 'someObject.someField': dbCmd.rename('someObject.renamedField') + } +}) +``` + +### 更新·数组操作符 + +#### push + +数组更新操作符。对一个值为数组的字段,往数组添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。 + + +##### 参数说明 + + +**position 说明** + + 要求必须同时有 `each` 参数存在。 + + 非负数代表从数组开始位置数的位置,从 0 开始计。如果数值大于等于数组长度,则视为在尾部添加。负数代表从数组尾部倒数的位置,比如 -1 就代表倒数第二个元素的位置。如果负数数值的绝对值大于等于数组长度,则视为从数组头部添加。 + + + +**sort 说明** + + 要求必须同时有 `each` 参数存在。给定 1 代表升序,-1 代表降序。 + + 如果数组元素是记录,则用 `{ <字段>: 1 | -1 }` 的格式表示根据记录中的什么字段做升降序排序。 + + + +**slice** 说明** + + 要求必须同时有 `each` 参数存在 + +|值 |说明 | +|:-: |:-: | +|0 |将字段更新为空数组 | +|正数 |数组只保留前 n 个元素| +|负数 |数组只保留后 n 个元素| + + +##### 示例 1:尾部添加元素 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push(['mini-program', 'cloud']) + } +}) +``` + +##### 示例 2:从第二个位置开始插入 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + position: 1, + }) + } +}) +``` + +##### 示例 3:排序 + 插入后对整个数组做排序 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + sort: 1, + }) + } +}) +``` +不插入,只对数组做排序 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push({ + each: [], + sort: 1, + }) + } +}) +``` +如果字段是对象数组,可以如下根据元素对象里的字段进行排序: + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push({ + each: [ + { name: 'miniprogram', weight: 8 }, + { name: 'cloud', weight: 6 }, + ], + sort: { + weight: 1, + }, + }) + } +}) +``` + +##### 示例 4:截断保留 + 插入后只保留后 2 个元素 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + slice: -2, + }) + } +}) +``` + +##### 示例 5:在指定位置插入、然后排序、最后只保留前 2 个元素 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + position: 1, + slice: 2, + sort: 1, + }) + } +}) +``` + +#### pop + +数组更新操作符,对一个值为数组的字段,将数组尾部元素删除 + + +##### 示例代码 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.pop() + } +}) +``` + +#### unshift + +数组更新操作符,对一个值为数组的字段,往数组头部添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。 + + +##### 示例代码 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.unshift(['mini-program', 'cloud']) + } +}) +``` + +#### shift + +数组更新操作符,对一个值为数组的字段,将数组头部元素删除。 + + +##### 示例代码 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.shift() + } +}) +``` + +#### pull + +数组更新操作符。给定一个值或一个查询条件,将数组中所有匹配给定值或查询条件的元素都移除掉。 + + +##### 示例代码 1:根据常量匹配移除 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.pull('database') + } +}) +``` + +##### 示例代码 2:根据查询条件匹配移除 + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.pull(dbCmd.in(['database', 'cloud'])) + } +}) +``` + +##### 示例代码 3:对象数组时,根据查询条件匹配移除 + 假设有字段 `places` 数组中的元素结构如下 + + +``` +{ + "type": string + "area": number + "age": number +} +``` + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + places: dbCmd.pull({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }) + } +}) +``` + +##### 示例代码 4:有嵌套对象的对象数组时,根据查询条件匹配移除 + 假设有字段 `cities` 数组中的元素结构如下 + + +``` +{ + "name": string + "places": Place[] +} +``` +`Place` 结构如下: + + +``` +{ + "type": string + "area": number + "age": number +} +``` +可用 `elemMatch` 匹配嵌套在对象数组里面的对象数组字段 places + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + cities: dbCmd.pull({ + places: dbCmd.elemMatch({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }) + }) + } +}) +``` + +#### pullAll + +数组更新操作符。给定一个值或一个查询条件,将数组中所有匹配给定值的元素都移除掉。跟 `pull` 的差别在于只能指定常量值、传入的是数组。 + + +##### 示例代码:根据常量匹配移除 + 从 tags 中移除所有 database 和 cloud 字符串 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.pullAll(['database', 'cloud']) + } +}) +``` + +#### addToSet + +数组更新操作符。原子操作。给定一个或多个元素,除非数组中已存在该元素,否则添加进数组。 + + +##### 示例代码 1:添加一个元素 + 如果 tags 数组中不包含 database,添加进去 + + +``` +const dbCmd = db.command +db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.addToSet('database') + } +}) +``` + +##### 示例代码 2:添加多个元素 + 需传入一个对象,其中有一个字段 `each`,其值为数组,每个元素就是要添加的元素 + + +``` + const dbCmd = db.command + db.collection('todos').doc('doc-id').update({ + data: { + tags: dbCmd.addToSet({ + each: ['database', 'cloud'] + }) + } + }) +``` + + ## 聚合操作符