Azure Cosmos で無料で作るハイスコアDB(3) : コア(SQL)

# はじめに

CosmosDBにコア(SQL)で、スコア登録、ハイスコア取得までできたので以下記録として残す。

利用開始までの手順は1回目記事参照：
[Azure Cosmos で無料で作るハイスコアDB(1)](https://qiita.com/kaku3/items/c64eb4acd97ab8e41592)

解説部分以外のソースはgit hub参照：
[cosmos-db サンプル](https://github.com/kaku3/the-sheep-in-the-desert/tree/master/azure/cosmos-db)

登録/取得処理が作成できたので、あとは AzureFunctions から呼び出すようにすれば利用開始できそうであるが、**使用感が悪い**のでCosmosDBは今回で終了にしてfirebaseに移行しようと思う。

# NoSQLと一言で言っても…

[Wikipedia](https://ja.wikipedia.org/wiki/NoSQL)

> NoSQL（一般に "Not only SQL" と解釈される）とは、関係データベース管理システム (RDBMS) 以外のデータベース管理システムを指すおおまかな分類語である。関係データベースを杓子定規に適用してきた長い歴史を打破し、それ以外の構造のデータベースの利用・発展を促進させようとする運動の標語としての意味合いを持つ。関係モデルではないデータストアの特徴として、固定されたスキーマに縛られないこと、関係モデルの結合操作を利用しないこと（場合によっては単にそのような機能が欠落しているだけ）、水平スケーラビリティが確保しやすい事が多いこと、トランザクション処理を利用できないものが多いことなどが挙げられる。学術的な世界では、この種のデータベースのことを構造型ストレージ (structured storage) と呼ぶことが多い[1][2][3][4]。

DynamoDBは、コンパネに入ると[テーブルの作成]ボタンがあり、テーブル位は普通あるものなのでしょうと思い込んだが、CosmosDBのコアSQLにはなかった。

![優秀なDynamoさん](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/d7bcebab-9853-7b41-840a-f2a3a4cd6a3f.png)


CosmosDBは、アカウント作成時にAPIとデータモデル種類を選択できて、その選択により**できることが異なる仕様**となっている。
これはネット上の断片的な情報をかき集めて作業するのが大変困難な仕様と思われたので、このまま継続利用するのを断念するに至った。

APIとデータモデル：
[Azure Cosmos で無料で作るハイスコアDB(2) : APIとデータモデルとAzureテーブル](https://qiita.com/kaku3/items/d3e675790ce36ee54435)


# データ構造

Cosmos DB のコア(SQL)のデータモデルは、`id`項目をプライマリーキーとして json を保存する key-value ストアである。
本記事では昭和のゲームのハイスコアを再現するため、名前とスコアが登録対象となるが、どちらも一意のキーとはならない。

```
{
    "id": <一意の値>
    "name": "test",
    "score": 123
}
```

よって、`id`に一意の値を設定するためにシリアル値などを生成する必要があるが、シリアル値を保存するテーブルを保持することができない。
そこで今回は、`{テーブル名}_{id}`といった値を`id`として設定することとした。

``` sequences.js
  {
    "id": "sequences_scores", // score テーブル用の sequence
    "no": 1   // 現在のシーケンス
  }
```

``` scores.js
  {
    "id": "scores_{sequenceより取得した一意の値}"
    "name": "SHEEP",
    "score": 1000
  },
  {
    "id": "scores_{sequenceより取得した一意の値}"
    "name": "SHEEP",    // 重複可
    "score": 900
  }
  ...
```

# DB作成

シンプルかつ使いやすいコマンド

```
async function createDatabase() {
  const { database } = await client.databases.createIfNotExists({
    id: databaseId
  })
  console.log(`Created database:\n${database.id}\n`)
}
```

# Container 作成

シンプルかつ使いやすいコマンド。partitionKey は少し気持ち悪い。

```
async function createContainer() {
  const { container } = await client
    .database(databaseId)
    .containers.createIfNotExists(
      { id: containerId, partitionKey },
      { offerThroughput: 400 }
    )
  console.log(`Created container:\n${config.container.id}\n`)
}
```

# upsert(insert or update)

`items.upsert` で、一意のidを持つjsonを登録。
同じidの項目があれば上書き。１件ずつしか登録できない。

```
async function upsertItems(itemBodies) {

  // 複数itemの配列を upsert して、Promise.all(ps)で完了待ち
  const items = client.database(databaseId).container(containerId).items
  const ps = itemBodies.map(body => items.upsert(body))
  await Promise.all(ps)

  console.log(`upserted items : `, itemBodies.map(o => o.id))
}
```

# select

SQLクエリで上位スコアを取得。
コア(SQL)ではSQLが使えるが、テーブルAPIでは使えない。

```
async function getHiScores(count) {
  console.group(`+ getHiScores(${count})`)
  const q = {
    query: `SELECT VALUE c FROM c ORDER BY c.score DESC OFFSET 0 LIMIT ${count}`,
    parameters: []
  }
  const query = client.database(databaseId).container(containerId).items.query(q)
  const { resources: rs } = await query.fetchAll()

  console.groupEnd()
  console.log(`- getHiScores(${count})`)
  return rs
}
```

# おわりに

簡単なハイスコア処理に必要なコードを一通り書いた。
SQLより不便なのは仕方ないが、テーブルがサポートされておらず`id`を工夫する実装を続けるのは大変ストレスに感じた。
何らかのログを投げ込む等、純粋に単一のデータを投げ込む先としてはよいのかと思ったが、AWSより安価ということもないのであれば、あえてCosmosDBを選択するメリットは感じられなかった。