Azure Cosmos DB - UPSERT

# 前提

Partition Key は`/pk`、Unique keys は`/col1, /col2`とする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/4aa3ba7b-3205-9402-29e8-09e48a085660.png)

# UPSERT

## 使用コード

UPSERT には SDK の [upsert](https://docs.microsoft.com/en-us/javascript/api/@azure/cosmos/items?view=azure-node-latest#@azure-cosmos-items-upsert) を使用する。

```javascript
const { CosmosClient } = require('@azure/cosmos');
const config = require('./config');

async function upsert() {
  const { connectionString, databaseId, containerId } = config;

  const client = new CosmosClient(connectionString);
  const database = client.database(databaseId);
  const container = database.container(containerId);

  try {
    const item = {
      // 内容は適時変更
      pk: 'A',
      id: '0001',
      col1: 1,
      col2: 2,
    };

    await container.items.upsert(item);
  } catch (err) {
    console.log(err.message);
  }
}

upsert();
```

## 挙動

競合するデータが存在しない場合は、INSERT、
競合するデータが存在する場合は、UPDATE
が基本的な UPSERT の挙動。

データの一意性、競合については、[前回記事](https://qiita.com/takmot/items/4409642147c2ba84842a)参照。

### 1．競合するデータが存在しない場合

UPSERT すると、以下のようにアイテムが追加される。

![2022-02-27-06-48-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/98275c40-5b37-9c19-6270-1be1a520b90c.png)

### 2．Partition Key が競合するデータが存在する場合

`pk`、`id` が一致するデータの UPSERT

`{"pk": "A", "id": "0001", "col1": 1, "col2": 2}` が存在する状態で、
`{"pk": "A", "id": "0001", "col1": 2, "col2": 3}` を UPSERT すると、
以下のようにアイテムが追加されることなく `col1`、`col2` の値が UPDATE される。

![2022-02-27-06-55-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/3a17365b-5c00-6b25-815b-e23185845128.png)

### 3．Unique keys が競合するデータが存在する場合

`pk` と、`col1`、`col2` が一致するデータの UPSERT

`{"pk": "A", "id": "0001", "col1": 1, "col2": 2}` が存在する状態で、
`{"pk": "A", "id": "0002", "col1": 1, "col2": 2, "col3": 3}` を UPSERT すると、
競合が発生する。

つまり、Unique keys の競合の場合は UPDATE されない。
そのため、Unique keys の競合に対しては [create](https://docs.microsoft.com/en-us/javascript/api/@azure/cosmos/items?view=azure-node-latest#@azure-cosmos-items-create) でも挙動は同じ。

競合例外発生時に UPDATE を実行すれば、UPSERT は実現可能。

# 一括 UPSERT

## 使用コード

一括 UPSERT には SDK の [bulk](https://docs.microsoft.com/en-us/javascript/api/@azure/cosmos/items?view=azure-node-latest#@azure-cosmos-items-bulk) を使用する。

```javascript
const { CosmosClient } = require('@azure/cosmos');
const config = require('./config');

async function bulkUpsert() {
  const { connectionString, databaseId, containerId } = config;

  const client = new CosmosClient(connectionString);
  const database = client.database(databaseId);
  const container = database.container(containerId);

  const operations = [
    {
      // INSERT
      operationType: 'Upsert',
      partitionKey: 'A',
      resourceBody: {
        pk: 'A',
        id: '0001',
        col1: 1,
        col2: 2,
      },
    },
    {
      // col1, col2 が同じため CONFLICT
      operationType: 'Upsert',
      partitionKey: 'A',
      resourceBody: {
        pk: 'A',
        id: '0002',
        col1: 1,
        col2: 2,
      },
    },
    {
      // col1, col2 が同じため CONFLICT
      operationType: 'Upsert',
      partitionKey: 'A',
      resourceBody: {
        pk: 'A',
        id: '0003',
        col1: 1,
        col2: 2,
        col3: 3,
      },
    },
    {
      // INSERT
      operationType: 'Upsert',
      partitionKey: 'A',
      resourceBody: {
        pk: 'A',
        id: '0004',
        col1: 1,
        col2: 3,
      },
    },
  ];
  bulkOptions = { continueOnError: true };

  try {
    await container.items.bulk(operations, bulkOptions);
  } catch (err) {
    console.log(err.message);
  }
}

bulkUpsert();
```

## 挙動

単発での UPSERT と同様。
ただし、競合が発生したアイテムに対して個別に例外処理を実行できないため、
UPSERT は実現不可。

上記コードのように
オプションで `{ continueOnError: true }` を指定することで例外を無視できるため、

競合するデータが存在しない場合は、INSERT、
競合するデータが存在する場合は、IGNORE

は実現可能。

上記コードを実行した場合、
`id: '0001'` は INSERT、
`id: '0002'` は col1, col2 が同じため CONFLICT
`id: '0003'` は col1, col2 が同じため CONFLICT
`id: '0004'` は INSERT
となる。

![2022-02-27-09-21-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/4163e02e-d3e4-d479-e022-ee77fa12b5ba.png)