云函数中支持对云数据库的全部功能的操作。本章节主要讲解如何在云函数内通过传统api操作数据库,如需在云函数内使用JQL语法操作数据库,请参考:[云函数内使用JQL语法](uniCloud/jql-cloud.md) ## 获取集合的引用@get-collection ```js const db = uniCloud.database(); // 获取 `user` 集合的引用 const collection = db.collection('user'); ``` ### 集合 Collection@collection 通过 `db.collection(name)` 可以获取指定集合的引用,在集合上可以进行以下操作 | 类型 | 接口 | 说明 | | -------- | ------- | ---------------------------------------------------------------------------------- | | 写 | add | 新增记录(触发请求) | | 计数 | count | 获取符合条件的记录条数 | | 读 | get | 获取集合中的记录,如果有使用 where 语句定义查询条件,则会返回匹配结果集 (触发请求) | | 引用 | doc | 获取对该集合中指定 id 的记录的引用 | | 查询条件 | where | 通过指定条件筛选出匹配的记录,可搭配查询指令(eq, gt, in, ...)使用 | | | skip | 跳过指定数量的文档,常用于分页,传入 offset | | | orderBy | 排序方式 | | | limit | 返回的结果集(文档数量)的限制,有默认值和上限值 | | | field | 指定需要返回的字段 | 查询及更新指令用于在 `where` 中指定字段需满足的条件,指令可通过 `db.command` 对象取得。 ### 记录 Record / Document@doc 通过 `db.collection(collectionName).doc(docId)` 可以获取指定集合上指定 _id 的记录的引用,在记录上可以进行以下操作 | 接口| 说明|| | ----| ------|----| | 写 | update | 局部更新记录(触发请求)只更新传入的字段。如果被更新的记录不存在,会直接返回更新失败| | | set | 覆写记录;会删除操作的记录中的所有字段,创建传入的字段。如果操作的记录不存在,会自动创建新的记录| | | remove| 删除记录(触发请求)| | 读 | get | 获取记录(触发请求)| doc(docId)方法的参数只能是字符串,即数据库默认的_id字段。 如需要匹配多个`_id`的记录,应使用where方法。可以在where方法里用in指令匹配一个包含`_id`的数组。 新增文档时数据库会自动生成_id字段,也可以自行将_id设置为其他值 ### 查询筛选指令 Query Command@query-command 以下指令挂载在 `db.command` 下 | 类型 | 接口 | 说明 | | -------- | ---- | ---------------------------------- | | 比较运算 | eq | 字段等于 == | | | neq | 字段不等于 != | | | gt | 字段大于 > | | | gte | 字段大于等于 >= | | | lt | 字段小于 < | | | lte | 字段小于等于 <= | | | in | 字段值在数组里 | | | nin | 字段值不在数组里 | | 逻辑运算 | and | 表示需同时满足指定的所有条件 | | | or | 表示需同时满足指定条件中的至少一个 | 如果你熟悉SQL,可查询[mongodb与sql语句对照表](https://blog.csdn.net/xinghebuluo/article/details/7012788/)进行学习。 ### 字段更新指令 Update Command@update-command 以下指令挂载在 `db.command` 下 | 类型 | 接口 | 说明 | | ---- | ------- | -------------------------------- | | 字段 | set | 设置字段值 | | | remove | 删除字段 | | | inc | 加一个数值,原子自增 | | | mul | 乘一个数值,原子自乘 | | | push | 数组类型字段追加尾元素,支持数组 | | | pop | 数组类型字段删除尾元素,支持数组 | | | shift | 数组类型字段删除头元素,支持数组 | | | unshift | 数组类型字段追加头元素,支持数组 | ## 新增文档@add 方法1: collection.add(data) 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | ---------------------------------------- | | data | object | array | 是 | {_id: '10001', 'name': 'Ben'} _id 非必填| 响应参数 单条插入时 | 参数| 类型 | 说明 | | ----| ------| ---------------------------------------- | |id | String|插入记录的id | 批量插入时 | 参数 | 类型 | 说明 | | ---- | ------| ---------------------------------------- | |ids | Array |批量插入所有记录的id | 示例: ```js // 单条插入数据 let res = await collection.add({ name: 'Ben' }) // 批量插入数据 let res = await collection.add([{ name: 'Alex' },{ name: 'Ben' },{ name: 'John' }]) ``` **Tips** - 云服务商为阿里云时,若集合不存在,调用add方法会自动创建集合。注意此方式创建的集合不带索引、表结构,尽量不要依赖此方式创建集合。 方法2: collection.doc().set(data) 也可通过 `set` 方法新增一个文档,需先取得文档引用再调用 `set` 方法。 如果文档不存在,`set` 方法会创建一个新文档。 **参数说明** | 参数 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | ---------------------------------------- | | data | object | 是 | 更新字段的Object,{'name': 'Ben'}| **响应参数** | 参数 | 类型 | 说明 | | ---- | ------| ---------------------------------------- | |updated | Number| 更新成功条数,数据更新前后没变化时也会返回1 | |upsertedId | String| 创建的文档id | ```js let res = await collection.doc('doc-id').set({ name: "Hey" }); ``` **注意** - 阿里云自动生成的_id是递增的,后创建的记录的_id总是大于先生成的_id。腾讯云自动生成的_id并非递增。 ## 查询文档@query 支持 `where()`、`limit()`、`skip()`、`orderBy()`、`get()`、`field()`、`count()` 等操作。 只有当调用`get()`时才会真正发送查询请求。 limit,即返回记录的最大数量,默认值为100,也就是不设置limit的情况下默认返回100条数据。limit最大为1000条。 如需查询更多数据,需要分页多次查询。 如果使用JQL语法传入getTree参数以返回树形数据也受上面的规则限制,不过此时limit方法仅对根节点生效(大量数据建议使用分层加载,不要使用getTree一次返回所有数据) **get响应参数** | 参数| 类型 | 说明 | | ----| ------| ---------------------------------------- | |data | Array | 查询结果数组 | ### 添加查询条件@where collection.where() **在聚合操作中请使用match** 设置过滤条件,where 可接收对象作为参数,表示筛选出拥有和传入对象相同的 key-value 的文档。比如筛选出所有类型为计算机的、内存为 8g 的商品: ```js let res = await db.collection('goods').where({ category: 'computer', type: { memory: 8, } }).get() ``` 如果要表达更复杂的查询,可使用高级查询指令,比如筛选出所有内存大于 8g 的计算机商品: ```js const dbCmd = db.command // 取指令 db.collection('goods').where({ category: 'computer', type: { memory: dbCmd.gt(8), // 表示大于 8 } }) ``` 在SQL里使用字符串表达式操作。但在NOSQL中使用json操作。这使得 等于 的表达,从 `=` 变成了 `:`;而大于的表达,从 `>` 变成了 `dbCmd.gt()` 所有的比较符,详见[表格](https://uniapp.dcloud.io/uniCloud/cf-database-dbcmd?id=%e6%9f%a5%e8%af%a2%e7%ad%9b%e9%80%89%e6%8c%87%e4%bb%a4-query-command) `where` 还可以使用正则表达式来查询文档,比如一下示例查询所有`name`字段以ABC开头的用户 ```js db.collection('user').where({ name: new RegExp('^ABC') }) ``` **按照数组内的值查询** mongoDB内按照数组内的值查询可以使用多种写法,以下面的数据为例 ```js { arr:[{ name: 'item-1', },{ name: 'item-2', }] } { arr:[{ name: 'item-3', },{ name: 'item-4', }] } ``` 如果想查询arr内第一个元素的name为item-1的记录可以使用如下写法 ```js const res = await db.collection('test').where({ 'arr.0.name': 'item-1' }) res = { data:[{ arr:[{ name: 'item-1', },{ name: 'item-2', }] }] } ``` 如果想查询arr内某个元素的name为item-1的记录(可以是数组内的任意一条name为item-1)可以使用如下写法 ```js const res = await db.collection('test').where({ 'arr.name': 'item-1' }) res = { data:[{ arr:[{ name: 'item-1', },{ name: 'item-2', }] }] } ``` ### 获取查询数量@count collection.count() ```js let res = await db.collection('goods').where({ category: 'computer', type: { memory: 8, } }).count() ``` 响应参数 | 字段 | 类型 | 必填 | 说明 | | --------- | ------- | ---- | ------------------------ | | total | Number | 否 | 计数结果 | **注意:** - 数据量很大的情况下,带条件运算count全表的性能会很差,尽量使用其他方式替代,比如新增一个字段专门用来存放总数。不加条件时count全表不存在性能问题。 ### 设置记录数量@limit collection.limit() 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------- | ---- | -------------- | | value | Number | 是 | 返回的数据条数 | 使用示例 ```js let res = await collection.limit(1).get() // 只返回第一条记录 ``` **注意** - limit不设置的情况下默认返回100条数据;设置limit有最大值,腾讯云限制为最大1000条,阿里云限制为最大500条。 ### 设置起始位置@skip collection.skip(value) 参数说明 | 参数 | 类型 | 必填 | 说明 | | ----- | ------- | ---- | -------------- | | value | Number | 是 | 跳过指定的位置,从位置之后返回数据 | 使用示例 ```js let res = await collection.skip(4).get() ``` **注意:数据量很大的情况下,skip性能会很差,尽量使用其他方式替代,参考:[skip性能优化](uniCloud/db-performance.md?id=skip)** ### 对结果排序@order-by collection.orderBy(field, orderType) 参数说明 | 参数 | 类型 | 必填 | 说明 | | --------- | ------ | ---- | ----------------------------------- | | field | string | 是 | 排序的字段 | | orderType | string | 是 | 排序的顺序,升序(asc) 或 降序(desc) | 如果需要对嵌套字段排序,需要用 "点表示法" 连接嵌套字段,比如 style.color 表示字段 style 里的嵌套字段 color。 同时也支持按多个字段排序,多次调用 orderBy 即可,多字段排序时的顺序会按照 orderBy 调用顺序先后对多个字段排序 使用示例 ```js let res = await collection.orderBy("name", "asc").get() ``` **注意** - 排序字段存在多个重复的值时排序后的分页结果,可能会出现某条记录在上一页出现又在下一页出现的情况。这时候可以通过指定额外的排序条件比如`.orderBy("name", "asc").orderBy("_id", "asc")`来规避这种情况。 ### 指定返回字段@field collection.field() 从查询结果中,过滤掉不需要的字段,或者指定要返回的字段。 参数说明 | 参数 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | --------------------------------------- | | - | object | 是 | 过滤字段对象,包含字段名和策略,不返回传false,返回传true | 使用示例 ```js collection.field({ 'age': true }) //只返回age字段、_id字段,其他字段不返回 ``` **注意** - field内指定是否返回某字段时,不可混用true/false。即{'a': true, 'b': false}是一种错误的参数格式 - 只有使用{ '_id': false }明确指定不要返回_id时才会不返回_id字段,否则_id字段一定会返回。 ### 查询指令@dbcmd 查询指令以dbCmd.开头,包括等于、不等于、大于、大于等于、小于、小于等于、in、nin、and、or。 下面的查询指令以以下数据集为例: ```json // goods表 [{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 },{ "type": { "brand": "X", "name": "X-01", "memory": 8, "cpu": 4.0 }, "category": "computer", "quarter": "2020 Q3", "price": 6500 },{ "type": { "brand": "S", "name": "S-01", "author": "S-01-A" }, "category": "book", "quarter": "2020 Q3", "price": 20 }] ``` #### eq 等于@dbcmd-eq 表示字段等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`。 事实上在uniCloud的数据库中,`等于`有两种写法。 比如筛选出所有2020 Q2季度的商品, 写法1:使用`:`来比较 ```js const myOpenID = "xxx" let res = await db.collection('articles').where({ quarter: '2020 Q2' }).get() // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 }] } ``` 写法2:使用指令`dbcmd.eq()` ```js const dbCmd = db.command const myOpenID = "xxx" let res = await db.collection('articles').where({ quarter: dbCmd.eq('2020 Q2') }).get() // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 }] } ``` 注意 `eq` 指令有更大的灵活性,可以用于表示字段等于某个对象的情况,比如: ```js // 这种写法表示匹配 type.brand == 'S' 且 type.name == 'S-01' let res = await db.collection('articles').where({ type: { brand: 'S', name: 'S-01' } }).get() // 查询返回值 { "data":[{ "type": { "brand": "S", "name": "S-01", "author": "S-01-A" }, "category": "book", "quarter": "2020 Q3", "price": 20 }] } // 这种写法表示 stat 对象等于 { brand: 'S', name: 'S-01' } // 对象中还有其他字段时无法匹配,例如:{ brand: 'S', name: 'S-01', author: 'S-01-A' } // 对象中字段顺序不一致也不能匹配,例如:{ name: 'S-01', brand: 'S' } const dbCmd = db.command let res = await db.collection('articles').where({ stat: dbCmd.eq({ brand: 'S', name: 'S-01' }) }).get() // 查询返回值 { "data":[] } ``` #### neq 不等于@dbcmd-neq 字段不等于。`neq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`。 如筛选出品牌不为 X 的计算机: ```js const dbCmd = db.command let res = await db.collection('goods').where({ category: 'computer', type: { brand: dbCmd.neq('X') }, }).get() // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 }] } ``` #### gt 大于@dbcmd-gt 字段大于指定值。 如筛选出价格大于 3000 的计算机: ```js const dbCmd = db.command let res = await db.collection('goods').where({ category: 'computer', price: dbCmd.gt(3000) }).get() // 查询返回值 { "data":[{ "type": { "brand": "X", "name": "X-01", "memory": 8, "cpu": 4.0 }, "category": "computer", "quarter": "2020 Q3", "price": 6500 }] } ``` #### gte 大于等于@dbcmd-gte 字段大于或等于指定值。 #### lt 小于@dbcmd-lt 字段小于指定值。 #### lte 小于等于@dbcmd-lte 字段小于或等于指定值。 #### in 在数组中@dbcmd-in 字段值在给定的数组中。 筛选出内存为 8g 或 16g 的计算机商品: ```js const dbCmd = db.command let res = await db.collection('goods').where({ category: 'computer', type: { memory: dbCmd.in([8, 16]) } }).get() // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 },{ "type": { "brand": "X", "name": "X-01", "memory": 8, "cpu": 4.0 }, "category": "computer", "quarter": "2020 Q3", "price": 6500 }] } ``` #### nin 不在数组中@dbcmd-nin 字段值不在给定的数组中。 筛选出内存不是 8g 或 16g 的计算机商品: ```js const dbCmd = db.command db.collection('goods').where({ category: 'computer', type: { memory: dbCmd.nin([8, 16]) } }) // 查询返回值 { "data":[] } ``` #### and 且@dbcmd-and 表示需同时满足指定的两个或以上的条件。 如筛选出内存大于 4g 小于 32g 的计算机商品: 流式写法: ```js const dbCmd = db.command db.collection('goods').where({ category: 'computer', type: { memory: dbCmd.gt(4).and(dbCmd.lt(32)) } }) // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 },{ "type": { "brand": "X", "name": "X-01", "memory": 8, "cpu": 4.0 }, "category": "computer", "quarter": "2020 Q3", "price": 6500 }] } ``` 前置写法: ```js const dbCmd = db.command db.collection('goods').where({ category: 'computer', type: { memory: dbCmd.and(dbCmd.gt(4), dbCmd.lt(32)) } }) ``` #### or 或@dbcmd-or 表示需满足所有指定条件中的至少一个。如筛选出价格小于 4000 或在 6000-8000 之间的计算机: 流式写法: ```js const dbCmd = db.command db.collection('goods').where({ category: 'computer', type: { price:dbCmd.lt(4000).or(dbCmd.gt(6000).and(dbCmd.lt(8000))) } }) // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 },{ "type": { "brand": "X", "name": "X-01", "memory": 8, "cpu": 4.0 }, "category": "computer", "quarter": "2020 Q3", "price": 6500 }] } ``` 前置写法: ```js const dbCmd = db.command db.collection('goods').where({ category: 'computer', type: { price: dbCmd.or(dbCmd.lt(4000), dbCmd.and(dbCmd.gt(6000), dbCmd.lt(8000))) } }) ``` 如果要跨字段 “或” 操作:(如筛选出内存 8g 或 cpu 3.2 ghz 的计算机) ```js const dbCmd = db.command db.collection('goods').where(dbCmd.or( { type: { memory: dbCmd.gt(8) } }, { type: { cpu: 3.2 } } )) // 查询返回值 { "data":[{ "type": { "brand": "A", "name": "A-01", "memory": 16, "cpu": 3.2 }, "category": "computer", "quarter": "2020 Q2", "price": 2500 },{ "type": { "brand": "X", "name": "X-01", "memory": 8, "cpu": 4.0 }, "category": "computer", "quarter": "2020 Q3", "price": 6500 }] } ``` ### 正则表达式查询@regexp #### db.RegExp 根据正则表达式进行筛选 例如下面可以筛选出 `version` 字段开头是 "数字+s" 的记录,并且忽略大小写: ```js // 可以直接使用正则表达式 db.collection('articles').where({ version: /^\ds/i }) // 也可以使用new RegExp db.collection('user').where({ name: new RegExp('^\\ds', 'i') }) // 或者使用new db.RegExp,这种方式阿里云不支持 db.collection('articles').where({ version: new db.RegExp({ regex: '^\\ds', // 正则表达式为 /^\ds/,转义后变成 '^\\ds' options: 'i' // i表示忽略大小写 }) }) ``` ### 查询数组字段@querywitharr 假设数据表class内有以下数据,可以使用下面两种方式查询数组内包含指定值 ```js { "_id": "1", "students": ["li","wang"] } { "_id": "2", "students": ["wang","li"] } { "_id": "3", "students": ["zhao","qian"] } ``` #### 指定下标查询 ```js const index = 1 const res = await db.collection('class').where({ ['students.' + index]: 'wang' }) .get() ``` ```js // 查询结果如下 { data: [{ "_id": "1", "students": ["li","wang"] }] } ``` #### 不指定下标查询 ```js const res = await db.collection('class').where({ students: 'wang' }) .get() ``` 查询结果如下 ```js { data: [{ "_id": "1", "students": ["li","wang"] },{ "_id": "1", "students": ["wang","li"] }] } ``` #### 数组内是对象 如果将上面class内的数据改为如下形式 ```js { "_id": "1", "students": [{ name: "li" },{ name: "wang" }] } { "_id": "2", "students": [{ name: "wang" },{ name: "li" }] } { "_id": "3", "students": [{ name: "zhao" },{ name: "qian" }] } ``` 不指定下标查询的写法可以修改为 ```js const res = await db.collection('class').where({ 'students.name': 'wang' }) .get() ``` 查询结果如下 ```js { data: [{ "_id": "1", "students": [{ name: "li" },{ name: "wang" }] }, { "_id": "2", "students": [{ name: "wang" },{ name: "li" }] }] } ``` ## 删除文档@remove **方式1 通过指定文档ID删除** collection.doc(_id).remove() ```js // 清理全部数据 let res = await collection.get() res.data.map(async(document) => { return await collection.doc(document.id).remove(); }); ``` **方式2 条件查找文档然后直接批量删除** collection.where().remove() ```js // 删除字段a的值大于2的文档 const dbCmd = db.command let res = await collection.where({ a: dbCmd.gt(2) }).remove() // 清理全部数据 const dbCmd = db.command let res = await collection.where({ _id: dbCmd.exists(true) }).remove() ``` 响应参数 | 字段 | 类型 | 必填 | 说明 | | --------- | ------- | ---- | ------------------------ | | deleted | Number | 否 | 删除的记录数量 | 示例:判断删除成功或失败,打印删除的记录数量 ```js const db = uniCloud.database(); db.collection("table1").doc("5f79fdb337d16d0001899566").remove() .then((res) => { console.log("删除成功,删除条数为: ",res.deleted); }) .catch((err) => { console.log( err.message ) }) .finally(() => { }) ``` ## 更新文档@update ### 更新指定文档@doc-update **使用腾讯云时更新方法必须搭配doc、where方法使用,`db.collection('test').update()`会报如下错误:`param should have required property 'query'`** collection.doc().update(Object data) > 未使用set、remove更新操作符的情况下,此方法不会删除字段,仅将更新数据和已有数据合并。 **参数说明** | 参数 | 类型 | 必填 | 说明 | | ---- | ------ | ---- | ---------------------------------------- | | data | object | 是 | 更新字段的Object,{'name': 'Ben'} _id 非必填| **响应参数** | 参数 | 类型 | 说明 | | ---- | ------| ---------------------------------------- | |updated| Number| 更新成功条数,数据更新前后没变化时会返回0 | ```js let res = await collection.doc('doc-id').update({ name: "Hey", count: { fav: 1 } }); ``` ```json // 更新前 { "_id": "doc-id", "name": "Hello", "count": { "fav": 0, "follow": 0 } } // 更新后 { "_id": "doc-id", "name": "Hey", "count": { "fav": 1, "follow": 0 } } ``` 更新数组时,已数组下标作为key即可,比如以下示例将数组arr内下标为1的值修改为 uniCloud ```js let res = await collection.doc('doc-id').update({ arr: { 1: "uniCloud" } }) ``` ```json // 更新前 { "_id": "doc-id", "arr": ["hello", "world"] } // 更新后 { "_id": "doc-id", "arr": ["hello", "uniCloud"] } ``` ### 更新文档,如果不存在则创建@doc-set collection.doc().set() **注意:** > 此方法会覆写已有字段,需注意与`update`表现不同,比如以下示例执行`set`之后`follow`字段会被删除 ```js let res = await collection.doc('doc-id').set({ name: "Hey", count: { fav: 1 } }) ``` ```json // 更新前 { "_id": "doc-id", "name": "Hello", "count": { "fav": 0, "follow": 0 } } // 更新后 { "_id": "doc-id", "name": "Hey", "count": { "fav": 1 } } ``` ### 批量更新文档@where-update `collection.update()` ```js const dbCmd = db.command let res = await collection.where({name: dbCmd.eq('hey')}).update({ age: 18, }) ``` ### 更新并返回更新后的数据@update-and-return > 新增于HBuilderX 3.2.0 此接口仅会操作一条数据,有多条数据匹配的情况下会只更新匹配的第一条并返回 **示例** ```js const db = uniCloud.database() await db.collection('test').where({ uid: '1' }).updateAndReturn({ score: db.command.inc(2) }) // 更新前 { _id: 'xx', uid: '1', score: 0 } // 更新后 { _id: 'xx', uid: '1', score: 2 } // 接口返回值 { updated: 1, doc: { _id: 'xx', uid: '1', score: 2 } } ``` **注意** - 使用updateAndReturn时,不可使用field方法 - 可以在事务中使用,可以使用`transaction.where().updateAndReturn()`以及`transaction.doc().updateAndReturn()` - 不同于update接口,此接口返回的updated不表示数据真的进行了更新 - 腾讯云暂不支持`doc().updateAndReturn()`的写法可以使用`where().updateAndReturn()`替代 ### 更新数组内指定下标的元素@update-arr-with-index ```js const res = await db.collection('query').doc('1').update({ // 更新students[1] ['students.' + 1]: { name: 'wang' } }) ``` ```js // 更新前 { "_id": "1", "students": [ { "name": "zhang" }, { "name": "li" } ] } // 更新后 { "_id": "1", "students": [ { "name": "zhang" }, { "name": "wang" } ] } ``` ### 更新数组内匹配条件的元素@update-arr-matched **注意:只可确定数组内只会被匹配到一个的时候使用** ```js const res = await db.collection('query').where({ 'students.id': '001' }).update({ // 将students内id为001的name改为li,$代表where内匹配到的数组项的序号 'students.$.name': 'li' }) ``` ```js // 更新前 { "_id": "1", "students": [ { "id": "001", "name": "zhang" }, { "id": "002", "name": "wang" } ] } // 更新后 { "_id": "1", "students": [ { "id": "001", "name": "li" }, { "id": "002", "name": "wang" } ] } ``` ### 更新操作符@update-operator 更多数据库操作符请查看[数据库操作符](#dbcmd) #### set@operator-set 更新指令。用于设定字段等于指定值。这种方法相比传入纯 JS 对象的好处是能够指定字段等于一个对象: ```js const dbCmd = db.command let res = await db.collection('photo').doc('doc-id').update({ count: dbCmd.set({ fav: 1, follow: 1 }) }) ``` ```json // 更新前 { "_id": "doc-id", "name": "Hello", "count": { "fav": 0, "follow": 0 } } // 更新后 { "_id": "doc-id", "name": "Hello", "count": { "fav": 1, "follow": 1 } } ``` #### inc@operator-inc 更新指令。用于指示字段自增某个值,这是个原子操作,使用这个操作指令而不是先读数据、再加、再写回的好处是: 1. 原子性:多个用户同时写,对数据库来说都是将字段加一,不会有后来者覆写前者的情况 2. 减少一次请求:不需先读再写 之后的 mul 指令同理。 在文章阅读数+1、收藏+1等很多场景会用到它。如给收藏的商品数量加一: ```js const dbCmd = db.command let res = await db.collection('user').where({ _id: 'my-doc-id' }).update({ count: { fav: dbCmd.inc(1) } }) ``` ```json // 更新前 { "_id": "my-doc-id", "name": "Hello", "count": { "fav": 0, "follow": 0 } } // 更新后 { "_id": "my-doc-id", "name": "Hello", "count": { "fav": 1, "follow": 0 } } ``` 请注意并没有直接提供减法操作符,如果要实现减法,仍通过inc实现。比如上述字段减1, ```js const dbCmd = db.command let res = await db.collection('user').where({ _id: 'my-doc-id' }).update({ count: { fav: dbCmd.inc(-1) } }) ``` #### mul@operator-mul 更新指令。用于指示字段自乘某个值。 以下示例将count内的fav字段乘10 ```js const dbCmd = db.command let res = await db.collection('user').where({ _id: 'my-doc-id' }).update({ count: { fav: dbCmd.mul(10) } }) ``` ```json // 更新前 { "_id": "my-doc-id", "name": "Hello", "count": { "fav": 2, "follow": 0 } } // 更新后 { "_id": "my-doc-id", "name": "Hello", "count": { "fav": 20, "follow": 0 } } ``` 请注意没有直接提供除法操作符,如果要实现除法,仍通过mul实现。比如上述字段除以10, ```js const dbCmd = db.command let res = await db.collection('user').where({ _id: 'my-doc-id' }).update({ count: { fav: dbCmd.mul(0.1) } }) ``` #### remove@operator-remove 更新指令。用于表示删除某个字段。如某人删除了自己一条商品评价中的评分: ```js const dbCmd = db.command let res = await db.collection('comments').doc('comment-id').update({ rating: dbCmd.remove() }) ``` ```json // 更新前 { "_id": "comment-id", "rating": 5, "comment": "xxx" } // 更新后 { "_id": "comment-id", "comment": "xxx" } ``` #### push@operator-push 向数组尾部追加元素,支持传入单个元素或数组 ```js const dbCmd = db.command let res = await db.collection('comments').doc('comment-id').update({ // users: dbCmd.push('aaa') users: dbCmd.push(['c', 'd']) }) ``` ```json // 更新前 { "_id": "comment-id", "users": ["a","b"] } // 更新后 { "_id": "comment-id", "users": ["a","b","c","d"] } ``` #### pop@operator-pop 删除数组尾部元素 ```js const dbCmd = db.command let res = await db.collection('comments').doc('comment-id').update({ users: dbCmd.pop() }) ``` ```json // 更新前 { "_id": "comment-id", "users": ["a","b"] } // 更新后 { "_id": "comment-id", "users": ["a"] } ``` #### unshift@operator-unshift 向数组头部添加元素,支持传入单个元素或数组。使用同push ```js const dbCmd = db.command let res = await db.collection('comments').doc('comment-id').update({ // users: dbCmd.push('aaa') users: dbCmd.unshift(['c', 'd']) }) ``` ```json // 更新前 { "_id": "comment-id", "users": ["a","b"] } // 更新后 { "_id": "comment-id", "users": ["c","d","a","b"] } ``` #### shift@operator-shift 删除数组头部元素。使用同pop ```js const dbCmd = db.command let res = await db.collection('comments').doc('comment-id').update({ users: dbCmd.shift() }) ``` ```json // 更新前 { "_id": "comment-id", "users": ["a","b"] } // 更新后 { "_id": "comment-id", "users": ["b"] } ``` ## GEO地理位置@geo 注意:**如果需要对类型为地理位置的字段进行搜索,一定要建立地理位置索引**。 ### GEO数据类型@geo-data-type #### Point@geo-point 用于表示地理位置点,用经纬度唯一标记一个点,这是一个特殊的数据存储类型。 签名:`Point(longitude: number, latitude: number)` 示例: ```js new db.Geo.Point(longitude, latitude) ``` #### LineString@geo-line-string 用于表示地理路径,是由两个或者更多的 `Point` 组成的线段。 签名:`LineString(points: Point[])` 示例: ```js new db.Geo.LineString([ new db.Geo.Point(lngA, latA), new db.Geo.Point(lngB, latB), // ... ]) ``` #### Polygon@geo-polygon 用于表示地理上的一个多边形(有洞或无洞均可),它是由一个或多个**闭环** `LineString` 组成的几何图形。 由一个环组成的 `Polygon` 是没有洞的多边形,由多个环组成的是有洞的多边形。对由多个环(`LineString`)组成的多边形(`Polygon`),第一个环是外环,所有其他环是内环(洞)。 签名:`Polygon(lines: LineString[])` 示例: ```js new db.Geo.Polygon([ new db.Geo.LineString(...), new db.Geo.LineString(...), // ... ]) ``` #### MultiPoint@geo-multi-point 用于表示多个点 `Point` 的集合。 签名:`MultiPoint(points: Point[])` 示例: ```js new db.Geo.MultiPoint([ new db.Geo.Point(lngA, latA), new db.Geo.Point(lngB, latB), // ... ]) ``` #### MultiLineString@geo-multi-line-string 用于表示多个地理路径 `LineString` 的集合。 签名:`MultiLineString(lines: LineString[])` 示例: ```js new db.Geo.MultiLineString([ new db.Geo.LineString(...), new db.Geo.LineString(...), // ... ]) ``` #### MultiPolygon@geo-multi-polygon 用于表示多个地理多边形 `Polygon` 的集合。 签名:`MultiPolygon(polygons: Polygon[])` 示例: ```js new db.Geo.MultiPolygon([ new db.Geo.Polygon(...), new db.Geo.Polygon(...), // ... ]) ``` ### GEO操作符@geo-operator #### geoNear@operator-geo-near 按从近到远的顺序,找出字段值在给定点的附近的记录。 签名: ```js db.command.geoNear(options: IOptions) interface IOptions { geometry: Point // 点的地理位置 maxDistance?: number // 选填,最大距离,米为单位 minDistance?: number // 选填,最小距离,米为单位 } ``` 示例: ```js let res = await db.collection('user').where({ location: db.command.geoNear({ geometry: new db.Geo.Point(lngA, latA), maxDistance: 1000, minDistance: 0 }) }).get() ``` #### geoWithin@operator-geo-within 找出字段值在指定 Polygon / MultiPolygon 内的记录,无排序 签名: ```js db.command.geoWithin(IOptions) interface IOptions { geometry: Polygon | MultiPolygon // 地理位置 } ``` 示例: ```js // 一个闭合的区域 const area = new Polygon([ new LineString([ new Point(lngA, latA), new Point(lngB, latB), new Point(lngC, latC), new Point(lngA, latA) ]), ]) // 搜索 location 字段在这个区域中的 user let res = await db.collection('user').where({ location: db.command.geoWithin({ geometry: area }) }).get() ``` #### geoIntersects@operator-geo-intersects 找出字段值和给定的地理位置图形相交的记录 签名: ```js db.command.geoIntersects(IOptions) interface IOptions { geometry: Point | LineString | MultiPoint | MultiLineString | Polygon | MultiPolygon // 地理位置 } ``` 示例: ```js // 一条路径 const line = new LineString([ new Point(lngA, latA), new Point(lngB, latB) ]) // 搜索 location 与这条路径相交的 user let res = await db.collection('user').where({ location: db.command.geoIntersects({ geometry: line }) }).get() ``` ## 事务@transaction 事务通常用来在某个数据库操作失败之后进行回滚。 > 事务因为要锁行,是有时间限制的。从事务开始到事务提交/回滚,时间不可超过10秒。另外注意:如果多条事务同时处理同一行数据,可能存在写冲突,进而导致失败。 ### runTransaction@run-transaction **阿里云不支持此用法,请换成startTransaction以使用事务** 发起事务。与`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` 的值。 **限制** 事务操作时为保障效率和并发性,只允许进行单记录操作,不允许进行批量操作,但可以在一个事务进行多次数据库操作。 - 对于修改和删除仅支持使用doc方法,不支持使用where方法。 - 新增时使用add方法一次只可以新增单条,不可新增多条,即不支持在add方法内传入数组 - 腾讯云没有限制where的使用,但是使用where修改或删除多条会导致无法回滚 **注意事项** - 开发者提供的事务执行函数正常返回时,uniCloud 会自动提交(`commit`)事务,请勿在事务执行函数内调用 `transaction.commit` 方法,该方法仅在通过 `db.startTransaction` 进行事务操作时使用 - 请注意transaction.doc().get()返回的data不是数组形式 **示例代码** 两个账户之间进行转账的简易示例 ```javascript const db = uniCloud.database() const dbCmd = 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) { try { const updateAAARes = await transaction.collection('account').doc('aaa').update({ amount: dbCmd.inc(-10) }) const updateBBBRes = await transaction.collection('account').doc('bbb').update({ amount: dbCmd.inc(10) }) const aaaEndRes = await transaction.collection('account').doc('aaa').get() if (aaaEndRes.data.amount < 0) { // 请注意transaction.doc().get()返回的data不是数组形式 await transaction.rollback(-100) } // 会作为 runTransaction resolve 的结果返回 return { aaaAccount: aaaEndRes.data.amount, } } catch(e) { // 会作为 runTransaction reject 的结果出去 await transaction.rollback(-100) } } else { await transaction.rollback(-100) } }) return { success: true, aaaAccount: result.aaaAccount, } } catch (e) { console.error(`transaction error`, e) return { success: false, error: e } } } ``` ### startTransaction@start-transaction 发起事务。与`runTransaction`作用类似,接收参数类型不同 **`startTransaction` 形式如下** ```javascript // 与runTransaction不同,startTransaction不接收参数 db.startTransaction() ``` **返回值** 返回一个`Promise`,此`Promise resolve`的结果为事务操作对象(**注意这里与runTransaction的区别**),其上可通过 `collection API` 操作数据库,通过 `commit`(**使用`startTransaction`需要主动`commit`**) 或 `rollback` 来结束或终止事务。 **限制** 事务操作时为保障效率和并发性,只允许进行单记录操作,不允许进行批量操作,但可以在一个事务进行多次数据库操作。 - 对于修改和删除仅支持使用doc方法,不支持使用where方法,updateAndReturn除外。 - 新增时使用add方法一次只可以新增单条,不可新增多条,即不支持在add方法内传入数组 - 腾讯云没有限制where的使用,但是使用where修改或删除多条会导致无法回滚 **注意** - 请注意transaction.doc().get()返回的data不是数组形式 **示例代码** 两个账户之间进行转账的简易示例 ```javascript const db = uniCloud.database() const dbCmd = db.command exports.main = async (event) => { const transaction = await db.startTransaction() try { 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({ amount: dbCmd.inc(-10) }) const updateBBBRes = await transaction.collection('account').doc('bbb').update({ amount: dbCmd.inc(10) }) const aaaEndRes = await transaction.collection('account').doc('aaa').get() if (aaaEndRes.data.amount < 0) { // 请注意transaction.doc().get()返回的data不是数组形式 await transaction.rollback(-100) return { success: false, error: `rollback`, rollbackCode: -100, } } else { await transaction.commit() console.log(`transaction succeeded`) return { success: true, aaaAccount: aaaRes.data.amount - 10, } } } else { return { success: false, error: `rollback`, rollbackCode: -100, } } } catch (e) { await transaction.rollback() console.error(`transaction error`, e) return { success: false, error: e } } } ```