云函数中支持对云数据库的全部功能的操作。本章节主要讲解如何在云函数内通过传统api操作数据库,如需在云函数内使用JQL语法操作数据库,请参考:云函数内使用JQL语法

# 获取集合的引用

const db = uniCloud.database();
// 获取 `user` 集合的引用
const collection = db.collection('user');

# 集合 Collection

通过 db.collection(name) 可以获取指定集合的引用,在集合上可以进行以下操作

类型 接口 说明
add 新增记录(触发请求)
计数 count 获取符合条件的记录条数
get 获取集合中的记录,如果有使用 where 语句定义查询条件,则会返回匹配结果集 (触发请求)
引用 doc 获取对该集合中指定 id 的记录的引用
查询条件 where 通过指定条件筛选出匹配的记录,可搭配查询指令(eq, gt, in, ...)使用
skip 跳过指定数量的文档,常用于分页,传入 offset
orderBy 排序方式
limit 返回的结果集(文档数量)的限制,有默认值和上限值
field 指定需要返回的字段

查询及更新指令用于在 where 中指定字段需满足的条件,指令可通过 db.command 对象取得。

# 记录 Record / Document

通过 db.collection(collectionName).doc(docId) 可以获取指定集合上指定 _id 的记录的引用,在记录上可以进行以下操作

接口 说明
update 局部更新记录(触发请求)只更新传入的字段。如果被更新的记录不存在,会直接返回更新失败
set 覆写记录;会删除操作的记录中的所有字段,创建传入的字段。如果操作的记录不存在,会自动创建新的记录
remove 删除记录(触发请求)
get 获取记录(触发请求)

doc(docId)方法的参数只能是字符串,即数据库默认的_id字段。

如需要匹配多个_id的记录,应使用where方法。可以在where方法里用in指令匹配一个包含_id的数组。

新增文档时数据库会自动生成_id字段,也可以自行将_id设置为其他值

# 查询筛选指令 Query Command

以下指令挂载在 db.command

类型 接口 说明
比较运算 eq 字段等于 ==
neq 字段不等于 !=
gt 字段大于 >
gte 字段大于等于 >=
lt 字段小于 <
lte 字段小于等于 <=
in 字段值在数组里
nin 字段值不在数组里
逻辑运算 and 表示需同时满足指定的所有条件
or 表示需同时满足指定条件中的至少一个

如果你熟悉SQL,可查询mongodb与sql语句对照表 (opens new window)进行学习。

# 字段更新指令 Update Command

以下指令挂载在 db.command

类型 接口 说明
字段 set 设置字段值
remove 删除字段
inc 加一个数值,原子自增
mul 乘一个数值,原子自乘
push 数组类型字段追加尾元素,支持数组
pop 数组类型字段删除尾元素,支持数组
shift 数组类型字段删除头元素,支持数组
unshift 数组类型字段追加头元素,支持数组

# 新增文档

方法1: collection.add(data)

参数说明

参数 类型 必填 说明
data object | array {_id: '10001', 'name': 'Ben'} _id 非必填

响应参数

单条插入时

参数 类型 说明
id String 插入记录的id

批量插入时

参数 类型 说明
ids Array 批量插入所有记录的id

示例:

// 单条插入数据
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
let res = await collection.doc('doc-id').set({
  name: "Hey"
});

注意

  • 阿里云自动生成的_id是递增的,后创建的记录的_id总是大于先生成的_id。腾讯云自动生成的_id并非递增。

# 查询文档

支持 where()limit()skip()orderBy()get()field()count() 等操作。

只有当调用get()时才会真正发送查询请求。

limit,即返回记录的最大数量,默认值为100,也就是不设置limit的情况下默认返回100条数据。

设置limit有最大值,腾讯云限制为最大1000条,阿里云限制为最大500条。

如需查询更多数据,需要分页多次查询。

如果使用JQL语法传入getTree参数以返回树形数据也受上面的规则限制,不过此时limit方法仅对根节点生效(大量数据建议使用分层加载,不要使用getTree一次返回所有数据)

get响应参数

参数 类型 说明
data Array 查询结果数组

# 添加查询条件

collection.where()

在聚合操作中请使用match

设置过滤条件,where 可接收对象作为参数,表示筛选出拥有和传入对象相同的 key-value 的文档。比如筛选出所有类型为计算机的、内存为 8g 的商品:

let res = await db.collection('goods').where({
  category: 'computer',
  type: {
    memory: 8,
  }
}).get()

如果要表达更复杂的查询,可使用高级查询指令,比如筛选出所有内存大于 8g 的计算机商品:

const dbCmd = db.command // 取指令
db.collection('goods').where({
  category: 'computer',
  type: {
    memory: dbCmd.gt(8), // 表示大于 8
  }
})

在SQL里使用字符串表达式操作。但在NOSQL中使用json操作。这使得 等于 的表达,从 = 变成了 :;而大于的表达,从 > 变成了 dbCmd.gt()

所有的比较符,详见表格 (opens new window)

where 还可以使用正则表达式来查询文档,比如一下示例查询所有name字段以ABC开头的用户

db.collection('user').where({
  name: new RegExp('^ABC')
})

按照数组内的值查询

mongoDB内按照数组内的值查询可以使用多种写法,以下面的数据为例

{
  arr:[{
    name: 'item-1',
  },{
    name: 'item-2',
  }]
}

{
  arr:[{
    name: 'item-3',
  },{
    name: 'item-4',
  }]
}

如果想查询arr内第一个元素的name为item-1的记录可以使用如下写法

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)可以使用如下写法

const res = await db.collection('test').where({
  'arr.name': 'item-1'
})

res = {
  data:[{
    arr:[{
      name: 'item-1',
    },{
      name: 'item-2',
    }]
  }]
}

# 获取查询数量

collection.count()

let res = await db.collection('goods').where({
  category: 'computer',
  type: {
    memory: 8,
  }
}).count()

响应参数

字段 类型 必填 说明
total Number 计数结果

注意:

  • 数据量很大的情况下,带条件运算count全表的性能会很差,尽量使用其他方式替代,比如新增一个字段专门用来存放总数。不加条件时count全表不存在性能问题。

# 设置记录数量

collection.limit()

参数说明

参数 类型 必填 说明
value Number 返回的数据条数

使用示例

let res = await collection.limit(1).get() // 只返回第一条记录

注意

  • limit不设置的情况下默认返回100条数据;设置limit有最大值,腾讯云限制为最大1000条,阿里云限制为最大500条。

# 设置起始位置

collection.skip(value)

参数说明

参数 类型 必填 说明
value Number 跳过指定的位置,从位置之后返回数据

使用示例

let res = await collection.skip(4).get()

注意:数据量很大的情况下,skip性能会很差,尽量使用其他方式替代,参考:skip性能优化

# 对结果排序

collection.orderBy(field, orderType)

参数说明

参数 类型 必填 说明
field string 排序的字段
orderType string 排序的顺序,升序(asc) 或 降序(desc)

如果需要对嵌套字段排序,需要用 "点表示法" 连接嵌套字段,比如 style.color 表示字段 style 里的嵌套字段 color。

同时也支持按多个字段排序,多次调用 orderBy 即可,多字段排序时的顺序会按照 orderBy 调用顺序先后对多个字段排序

使用示例

let res = await collection.orderBy("name", "asc").get()

注意

  • 排序字段存在多个重复的值时排序后的分页结果,可能会出现某条记录在上一页出现又在下一页出现的情况。这时候可以通过指定额外的排序条件比如.orderBy("name", "asc").orderBy("_id", "asc")来规避这种情况。

# 指定返回字段

collection.field()

从查询结果中,过滤掉不需要的字段,或者指定要返回的字段。

参数说明

参数 类型 必填 说明
- object 过滤字段对象,包含字段名和策略,不返回传false,返回传true

使用示例

collection.field({ 'age': true }) //只返回age字段、_id字段,其他字段不返回

注意

  • field内指定是否返回某字段时,不可混用true/false。即{'a': true, 'b': false}是一种错误的参数格式
  • 只有使用{ '_id': false }明确指定不要返回_id时才会不返回_id字段,否则_id字段一定会返回。

# 查询指令

查询指令以dbCmd.开头,包括等于、不等于、大于、大于等于、小于、小于等于、in、nin、and、or。

下面的查询指令以以下数据集为例:

// 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 等于

表示字段等于某个值。eq 指令接受一个字面量 (literal),可以是 number, boolean, string, object, array

事实上在uniCloud的数据库中,等于有两种写法。

比如筛选出所有2020 Q2季度的商品,

写法1:使用:来比较

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()

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 指令有更大的灵活性,可以用于表示字段等于某个对象的情况,比如:

// 这种写法表示匹配 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 不等于

字段不等于。neq 指令接受一个字面量 (literal),可以是 number, boolean, string, object, array

如筛选出品牌不为 X 的计算机:

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 大于

字段大于指定值。

如筛选出价格大于 3000 的计算机:

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 大于等于

字段大于或等于指定值。

# lt 小于

字段小于指定值。

# lte 小于等于

字段小于或等于指定值。

# in 在数组中

字段值在给定的数组中。

筛选出内存为 8g 或 16g 的计算机商品:

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 不在数组中

字段值不在给定的数组中。

筛选出内存不是 8g 或 16g 的计算机商品:

const dbCmd = db.command
db.collection('goods').where({
  category: 'computer',
  type: {
    memory: dbCmd.nin([8, 16])
  }
})

// 查询返回值
{
  "data":[]
}

# and 且

表示需同时满足指定的两个或以上的条件。

如筛选出内存大于 4g 小于 32g 的计算机商品:

流式写法:

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
  }]
}

前置写法:

const dbCmd = db.command
db.collection('goods').where({
  category: 'computer',
  type: {
    memory: dbCmd.and(dbCmd.gt(4), dbCmd.lt(32))
  }
})

# or 或

表示需满足所有指定条件中的至少一个。如筛选出价格小于 4000 或在 6000-8000 之间的计算机:

流式写法:

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
  }]
}

前置写法:

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 的计算机)

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
  }]
}

# 正则表达式查询

# db.RegExp

根据正则表达式进行筛选

例如下面可以筛选出 version 字段开头是 "数字+s" 的记录,并且忽略大小写:

// 可以直接使用正则表达式
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表示忽略大小写
  }) 
})

# 查询数组字段

假设数据表class内有以下数据,可以使用下面两种方式查询数组内包含指定值

{
  "_id": "1",
  "students": ["li","wang"]
}
{
  "_id": "2",
  "students": ["wang","li"]
}
{
  "_id": "3",
  "students": ["zhao","qian"]
}

# 指定下标查询

const index = 1
const res = await db.collection('class').where({
  ['students.' + index]: 'wang'
})
.get()
// 查询结果如下
{
  data: [{
    "_id": "1",
    "students": ["li","wang"]
  }]
}

# 不指定下标查询

const res = await db.collection('class').where({
  students: 'wang'
})
.get()

查询结果如下

{
  data: [{
    "_id": "1",
    "students": ["li","wang"]
  },{
    "_id": "1",
    "students": ["wang","li"]
  }]
}

# 数组内是对象

如果将上面class内的数据改为如下形式

{
  "_id": "1",
  "students": [{
    name: "li"
  },{
    name: "wang"
  }]
}
{
  "_id": "2",
  "students": [{
    name: "wang"
  },{
    name: "li"
  }]
}
{
  "_id": "3",
  "students": [{
    name: "zhao"
  },{
    name: "qian"
  }]
}

不指定下标查询的写法可以修改为

const res = await db.collection('class').where({
  'students.name': 'wang'
})
.get()

查询结果如下

{
  data: [{
    "_id": "1",
    "students": [{
      name: "li"
    },{
      name: "wang"
    }]
  },
  {
    "_id": "2",
    "students": [{
      name: "wang"
    },{
      name: "li"
    }]
  }]
}

# 删除文档

方式1 通过指定文档ID删除

collection.doc(_id).remove()

// 清理全部数据
let res = await collection.get()
res.data.map(async(document) => {
  return await collection.doc(document.id).remove();
});

方式2 条件查找文档然后直接批量删除

collection.where().remove()

// 删除字段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 删除的记录数量

示例:判断删除成功或失败,打印删除的记录数量

const db = uniCloud.database();
db.collection("table1").doc("5f79fdb337d16d0001899566").remove()
	.then((res) => {
		console.log("删除成功,删除条数为: ",res.deleted);
	})
	.catch((err) => {
		console.log( err.message )
	})
	.finally(() => {
		
	})

# 更新文档

# 更新指定文档

使用腾讯云时更新方法必须搭配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
let res = await collection.doc('doc-id').update({
  name: "Hey",
  count: {
    fav: 1
  }
});
// 更新前
{
  "_id": "doc-id",
  "name": "Hello",
  "count": {
    "fav": 0,
    "follow": 0
  }
}

// 更新后
{
  "_id": "doc-id",
  "name": "Hey",
  "count": {
    "fav": 1,
    "follow": 0
  }
}

更新数组时,已数组下标作为key即可,比如以下示例将数组arr内下标为1的值修改为 uniCloud

let res = await collection.doc('doc-id').update({
  arr: {
    1: "uniCloud"
  }
})
// 更新前
{
  "_id": "doc-id",
  "arr": ["hello", "world"]
}
// 更新后
{
  "_id": "doc-id",
  "arr": ["hello", "uniCloud"]
}

# 更新文档,如果不存在则创建

collection.doc().set()

注意:

此方法会覆写已有字段,需注意与update表现不同,比如以下示例执行set之后follow字段会被删除

let res = await collection.doc('doc-id').set({
  name: "Hey",
  count: {
    fav: 1
  }
})
// 更新前
{
  "_id": "doc-id",
  "name": "Hello",
  "count": {
    "fav": 0,
    "follow": 0
  }
}

// 更新后
{
  "_id": "doc-id",
  "name": "Hey",
  "count": {
    "fav": 1
  }
}

# 批量更新文档

collection.update()

const dbCmd = db.command
let res = await collection.where({name: dbCmd.eq('hey')}).update({
  age: 18,
})

# 更新并返回更新后的数据

新增于HBuilderX 3.2.0

此接口仅会操作一条数据,有多条数据匹配的情况下会只更新匹配的第一条并返回

示例

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()替代

# 更新数组内指定下标的元素

const res = await db.collection('query').doc('1').update({
  // 更新students[1]
  ['students.' + 1]: {
    name: 'wang'
  }
})
// 更新前
{
  "_id": "1",
  "students": [
    {
      "name": "zhang"
    },
    {
      "name": "li"
    }
  ]
}

// 更新后
{
  "_id": "1",
  "students": [
    {
      "name": "zhang"
    },
    {
      "name": "wang"
    }
  ]
}

# 更新数组内匹配条件的元素

注意:只可确定数组内只会被匹配到一个的时候使用

const res = await db.collection('query').where({
	'students.id': '001'
}).update({
  // 将students内id为001的name改为li,$代表where内匹配到的数组项的序号
	'students.$.name': 'li'
})
// 更新前
{
  "_id": "1",
  "students": [
    {
      "id": "001",
      "name": "zhang"
    },
    {
      "id": "002",
      "name": "wang"
    }
  ]
}

// 更新后
{
  "_id": "1",
  "students": [
    {
      "id": "001",
      "name": "li"
    },
    {
      "id": "002",
      "name": "wang"
    }
  ]
}

# 更新操作符

更多数据库操作符请查看数据库操作符

# set

更新指令。用于设定字段等于指定值。这种方法相比传入纯 JS 对象的好处是能够指定字段等于一个对象:

const dbCmd = db.command
let res = await db.collection('photo').doc('doc-id').update({
  count: dbCmd.set({
    fav: 1,
    follow: 1
  })
})
// 更新前
{
  "_id": "doc-id",
  "name": "Hello",
  "count": {
    "fav": 0,
    "follow": 0
  }
}

// 更新后
{
  "_id": "doc-id",
  "name": "Hello",
  "count": {
    "fav": 1,
    "follow": 1
  }
}

# inc

更新指令。用于指示字段自增某个值,这是个原子操作,使用这个操作指令而不是先读数据、再加、再写回的好处是:

  1. 原子性:多个用户同时写,对数据库来说都是将字段加一,不会有后来者覆写前者的情况
  2. 减少一次请求:不需先读再写

之后的 mul 指令同理。

在文章阅读数+1、收藏+1等很多场景会用到它。如给收藏的商品数量加一:

const dbCmd = db.command

let res = await db.collection('user').where({
  _id: 'my-doc-id'
}).update({
  count: {
    fav: dbCmd.inc(1)
  }
})
// 更新前
{
  "_id": "my-doc-id",
  "name": "Hello",
  "count": {
    "fav": 0,
    "follow": 0
  }
}

// 更新后
{
  "_id": "my-doc-id",
  "name": "Hello",
  "count": {
    "fav": 1,
    "follow": 0
  }
}

请注意并没有直接提供减法操作符,如果要实现减法,仍通过inc实现。比如上述字段减1,

const dbCmd = db.command

let res = await db.collection('user').where({
  _id: 'my-doc-id'
}).update({
  count: {
    fav: dbCmd.inc(-1)
  }
})

# mul

更新指令。用于指示字段自乘某个值。

以下示例将count内的fav字段乘10

const dbCmd = db.command

let res = await db.collection('user').where({
  _id: 'my-doc-id'
}).update({
  count: {
    fav: dbCmd.mul(10)
  }
})
// 更新前
{
  "_id": "my-doc-id",
  "name": "Hello",
  "count": {
    "fav": 2,
    "follow": 0
  }
}

// 更新后
{
  "_id": "my-doc-id",
  "name": "Hello",
  "count": {
    "fav": 20,
    "follow": 0
  }
}

请注意没有直接提供除法操作符,如果要实现除法,仍通过mul实现。比如上述字段除以10,

const dbCmd = db.command

let res = await db.collection('user').where({
  _id: 'my-doc-id'
}).update({
  count: {
    fav: dbCmd.mul(0.1)
  }
})

# remove

更新指令。用于表示删除某个字段。如某人删除了自己一条商品评价中的评分:

const dbCmd = db.command
let res = await db.collection('comments').doc('comment-id').update({
  rating: dbCmd.remove()
})
// 更新前
{
  "_id": "comment-id",
  "rating": 5,
  "comment": "xxx"
}

// 更新后
{
  "_id": "comment-id",
  "comment": "xxx"
}

# push

向数组尾部追加元素,支持传入单个元素或数组

const dbCmd = db.command

let res = await db.collection('comments').doc('comment-id').update({
  // users: dbCmd.push('aaa')
  users: dbCmd.push(['c', 'd'])
})
// 更新前
{
  "_id": "comment-id",
  "users": ["a","b"]
}

// 更新后
{
  "_id": "comment-id",
  "users": ["a","b","c","d"]
}

# pop

删除数组尾部元素

const dbCmd = db.command

let res = await db.collection('comments').doc('comment-id').update({
  users: dbCmd.pop()
})
// 更新前
{
  "_id": "comment-id",
  "users": ["a","b"]
}

// 更新后
{
  "_id": "comment-id",
  "users": ["a"]
}

# unshift

向数组头部添加元素,支持传入单个元素或数组。使用同push

const dbCmd = db.command

let res = await db.collection('comments').doc('comment-id').update({
  // users: dbCmd.push('aaa')
  users: dbCmd.unshift(['c', 'd'])
})
// 更新前
{
  "_id": "comment-id",
  "users": ["a","b"]
}

// 更新后
{
  "_id": "comment-id",
  "users": ["c","d","a","b"]
}

# shift

删除数组头部元素。使用同pop

const dbCmd = db.command

let res = await db.collection('comments').doc('comment-id').update({
  users: dbCmd.shift()
})
// 更新前
{
  "_id": "comment-id",
  "users": ["a","b"]
}

// 更新后
{
  "_id": "comment-id",
  "users": ["b"]
}

# GEO地理位置

注意:如果需要对类型为地理位置的字段进行搜索,一定要建立地理位置索引

# GEO数据类型

# Point

用于表示地理位置点,用经纬度唯一标记一个点,这是一个特殊的数据存储类型。

签名:Point(longitude: number, latitude: number)

示例:

new db.Geo.Point(longitude, latitude)

# LineString

用于表示地理路径,是由两个或者更多的 Point 组成的线段。

签名:LineString(points: Point[])

示例:

new db.Geo.LineString([
  new db.Geo.Point(lngA, latA),
  new db.Geo.Point(lngB, latB),
  // ...
])

# Polygon

用于表示地理上的一个多边形(有洞或无洞均可),它是由一个或多个闭环 LineString 组成的几何图形。

由一个环组成的 Polygon 是没有洞的多边形,由多个环组成的是有洞的多边形。对由多个环(LineString)组成的多边形(Polygon),第一个环是外环,所有其他环是内环(洞)。

签名:Polygon(lines: LineString[])

示例:

new db.Geo.Polygon([
  new db.Geo.LineString(...),
  new db.Geo.LineString(...),
  // ...
])

# MultiPoint

用于表示多个点 Point 的集合。

签名:MultiPoint(points: Point[])

示例:

new db.Geo.MultiPoint([
  new db.Geo.Point(lngA, latA),
  new db.Geo.Point(lngB, latB),
  // ...
])

# MultiLineString

用于表示多个地理路径 LineString 的集合。

签名:MultiLineString(lines: LineString[])

示例:

new db.Geo.MultiLineString([
  new db.Geo.LineString(...),
  new db.Geo.LineString(...),
  // ...
])

# MultiPolygon

用于表示多个地理多边形 Polygon 的集合。

签名:MultiPolygon(polygons: Polygon[])

示例:

new db.Geo.MultiPolygon([
  new db.Geo.Polygon(...),
  new db.Geo.Polygon(...),
  // ...
])

# GEO操作符

# geoNear

按从近到远的顺序,找出字段值在给定点的附近的记录。

签名:

db.command.geoNear(options: IOptions)

interface IOptions {
  geometry: Point // 点的地理位置
  maxDistance?: number // 选填,最大距离,米为单位
  minDistance?: number // 选填,最小距离,米为单位
}

示例:

let res = await db.collection('user').where({
  location: db.command.geoNear({
    geometry: new db.Geo.Point(lngA, latA),
    maxDistance: 1000,
    minDistance: 0
  })
}).get()

# geoWithin

找出字段值在指定 Polygon / MultiPolygon 内的记录,无排序

签名:

db.command.geoWithin(IOptions)

interface IOptions {
  geometry: Polygon | MultiPolygon // 地理位置
}

示例:

// 一个闭合的区域
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

找出字段值和给定的地理位置图形相交的记录

签名:

db.command.geoIntersects(IOptions)

interface IOptions {
  geometry: Point | LineString | MultiPoint | MultiLineString | Polygon | MultiPolygon // 地理位置
}

示例:

// 一条路径
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()

# 事务

事务通常用来在某个数据库操作失败之后进行回滚。

事务因为要锁行,是有时间限制的。从事务开始到事务提交/回滚,时间不可超过10秒。另外注意:如果多条事务同时处理同一行数据,可能存在写冲突,进而导致失败。

# runTransaction

阿里云不支持此用法,请换成startTransaction以使用事务

发起事务。与startTransaction作用类似,接收参数类型不同

runTransaction 的形式如下:

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不是数组形式

示例代码

两个账户之间进行转账的简易示例

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

发起事务。与runTransaction作用类似,接收参数类型不同

startTransaction 形式如下

// 与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不是数组形式

示例代码

两个账户之间进行转账的简易示例

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
    }
  }
}