# 更新数据

在这章节我们一起看看如何使用数据库 API 完成数据更新，在本节中我们还是沿用[读取数据](read)章节中使用的数据。

更新数据主要有两个方法：

| API    | 说明                   |
|--------|------------------------|
| update | 局部更新一个或多个记录 |
| set    | 替换更新一个记录       |

### 局部更新

使用 `update` 方法可以局部更新一个记录或一个集合中的记录，局部更新意味着只有指定的字段会得到更新，其他字段不受影响。

比如我们可以用以下代码将一个待办事项置为已完成：

```js
db.collection('todos').doc('todo-identifiant-aleatoire').update({
  // data 传入需要局部更新的数据
  data: {
    // 表示将 done 字段置为 true
    done: true
  },
  success: function(res) {
    console.log(res.data)
  }
})
```

除了用指定值更新字段外，数据库 API 还提供了一系列的更新指令用于执行更复杂的更新操作，更新指令可以通过 `db.command` 取得：

| 更新指令                             | 说明                                   |
|---------------------------------|----------------------------------------|
| set         | 设置字段为指定值                       |
| remove   | 删除字段                               |
| inc         | 原子自增字段值                         |
| mul         | 原子自乘字段值                         |
| push       | 如字段值为数组，往数组尾部增加指定值   |
| pop         | 如字段值为数组，从数组尾部删除一个元素 |
| shift     | 如字段值为数组，从数组头部删除一个元素 |
| unshift | 如字段值为数组，往数组头部增加指定值   |

比如我们可以将一个待办事项的进度 +10%：

```js
const _ = db.command
db.collection('todos').doc('todo-identifiant-aleatoire').update({
  data: {
    // 表示指示数据库将字段自增 10
    progress: _.inc(10)
  },
  success: function(res) {
    console.log(res.data)
  }
})
```

用 `inc` 指令而不是取出值、加 10 再写进去的好处在于这个写操作是个原子操作，不会受到并发写的影响，比如同时有两名用户 A 和 B 取了同一个字段值，然后分别加上 10 和 20 再写进数据库，那么这个字段最终结果会是加了 20 而不是 30。如果使用 `inc` 指令则不会有这个问题。

如果字段是个数组，那么我们可以使用 `push`、`pop`、`shift` 和 `unshift` 对数组进行原子更新操作，比如给一条待办事项加多一个标签：

```js
const _ = db.command
db.collection('todos').doc('todo-identifiant-aleatoire').update({
  data: {
    tags: _.push('mini-program')
  },
  success: function(res) {
    console.log(res.data)
  }
})
```

可能读者已经注意到我们提供了 `set` 指令，这个指令有什么用呢？这个指令的用处在于更新一个字段值为另一个对象。比如如下语句是更新 `style.color` 字段为 'blue' 而不是把 `style` 字段更新为 `{ color: 'blue' }` 对象：

```js
const _ = db.command
db.collection('todos').doc('todo-identifiant-aleatoire').update({
  data: {
    style: {
      color: 'blue'
    }
  },
  success: function(res) {
    console.log(res.data)
  }
})
```

如果需要将这个 `style` 字段更新为另一个对象，可以使用 `set` 指令：

```js
const _ = db.command
db.collection('todos').doc('todo-identifiant-aleatoire').update({
  data: {
    style: _.set({
      color: 'blue'
    })
  },
  success: function(res) {
    console.log(res.data)
  }
})
```

如果需要更新多个数据，需在 Server 端进行操作（云函数），在 `where` 语句后同样的调用 `update` 方法即可，比如将所有未完待办事项的进度加 10%：

```js
// 使用了 async await 语法
const cloud = require('wx-server-sdk')
const db = cloud.database()
const _ = db.command

exports.main = async (event, context) => {
  try {
    return await db.collection('todos').where({
      done: false
    })
    .update({
      data: {
        progress: _.inc(10)
      },
    })
  } catch(e) {
    console.error(e)
  }
}
```

更完整详细的更新指令可以参考[数据库 Command API 文档](/minigame/dev/wxcloud/reference-sdk-api/database/Command)

### 替换更新

如果需要替换更新一条记录，可以在记录上使用 `set` 方法，替换更新意味着用传入的对象替换指定的记录：

```js
const _ = db.command
db.collection('todos').doc('todo-identifiant-aleatoire').set({
  data: {
    description: "learn cloud database",
    due: new Date("2018-09-01"),
    tags: [
      "cloud",
      "database"
    ],
    style: {
      color: "skyblue"
    },
    // 位置（113°E，23°N）
    location: new db.Geo.Point(113, 23),
    done: false
  },
  success: function(res) {
    console.log(res.data)
  }
})
```

如果指定 ID 的记录不存在，则会自动创建该记录，该记录将拥有指定的 ID。
