MongoDB の unique index 整理

# unique index でコレクションに一意制約を課す

まず unique index をセットする。

```js
> db.books.createIndex({title: 1}, {unique: true})
{
        "createdCollectionAutomatically" : true,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

```

上記のコマンドで `books` コレクションが `field: title` について一意制約を課されている状態になる。
この状態で、同じ `title` のドキュメントを2件作成しようと試みると、2件目で失敗する。

```js
> db.books.insert({title: '猫でもわかる MongoDB'})
WriteResult({ "nInserted" : 1 })
> db.books.insert({title: '猫でもわかる MongoDB'})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: title_1 dup key: { title: \"猫でもわかる MongoDB\" }"
        }
})
```

ちなみにこの状態では Not NULL 制約はついていないので、 `title` のないドキュメントは作ることができる。

```js
> db.books.insert({price: 3000})
WriteResult({ "nInserted" : 1 })
```

`title: null` のドキュメントも一意制約の対象になるので、2件目は作れない。

```js
> db.books.insert({price: 5000})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: title_1 dup key: { title: null }"
        }
})
```

# 複数のフィールドに対して unique index

下記のように設定すれば `title` と `price` が「どちらも同じ」ドキュメントが2件以上作れなくなる。

```js
> db.books.createIndex({title: 1, price: 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
```

- `price: 3000` のデータが1件ある状態でも、 `title` が異なれば `price: 3000` のデータは作れる
- `title: "ユニークインデックス再入門"` のデータが存在しても `price` が異なればデータは作れる
- `price`, `title` の両方が同じデータは作れない

```js
> db.books.find()
{ "_id" : ObjectId("5dd6bdb8b2e406088dafa5a7"), "title" : "猫でもわかる MongoDB" }
{ "_id" : ObjectId("5dd74a59b2e406088dafa5a9"), "price" : 3000 }
> db.books.insert({price: 3000, title: "ユニークインデックス再入門"})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 4000, title: "ユニークインデックス再入門"})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 3000, title: "ユニークインデックス再入門"})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: title_1_price_1 dup key: { title: \"ユニークインデックス再入門\", price: 3000.0 }"
        }
})
```

# ハッシュの子要素で unique index

ハッシュの子要素でも一意制約を設定できる。

```js
> db.books.createIndex({'author.name': 1}, {unique: true})
{
        "createdCollectionAutomatically" : true,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
```

この場合 `author.name` が同じドキュメントを2件作成することができない。

```js
> db.books.insert({price: 1000, title: 'NoSQL 徹底比較 2019', author: {name: '山下一郎'}})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 1200, title: 'NoSQL 徹底比較 2018', author: {name: '山下一郎'}})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: author.name_1 dup key: { author.name: \"山下一郎\" }"
        }
})
```

ネストしても同様なので、ハッシュの子要素の子要素でも一意制約を設定できる。

```js
> db.books.createIndex({'author.name.alias': 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
```

```js
> db.books.insert({price: 1000, title: 'MongoDB アップデート 2019', author: {name: {alias: '一郎さん'}}})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 900, title: 'MongoDB アップデート 2018', author: {name: {alias: '一郎さん'}}})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: author.name_1 dup key: { author.name: { alias: \"一郎さん\" } }"
        }
})
```

# 複数のハッシュの子要素で unique index

ハッシュの子要素で複数フィールドの unique index を設定することもできる。

```js
> db.books.createIndex({'author.name': 1, 'author.age': 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
```

上に記載した事例と同様に、対象のフィールド全てが一致する場合に一意制約違反になる。

```js
> db.books.insert({price: 2000, title: '1から始めるデータベース', author: {name: '山下二郎', age: 32}})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 2500, title: 'データベーススペシャリスト試験参考書', author: {name: '山下二郎', age: 33}})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 3000, title: 'ビッグデータの取り扱い説明書', author: {name: '山下二郎', age: 32}})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: author.name_1_author.age_1 dup key: { author.name: \"山下二郎\", author.age: 32.0 }"
        }
})
```

以下のように、異なるハッシュの子要素の組み合わせでも問題ない。

```js
> db.books.createIndex({'author.name': 1, 'publisher.name': 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
```

# 配列の子要素で unique index

配列の子要素でも一意制約を設定することができる。
ただし、注意が必要だ。

```js
> db.books.createIndex({'authors.name': 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
```

このような unique index を設定する。
既に存在する `authors.name` を含んだドキュメントは生成に失敗する。

```js
> db.books.insert({price: 12000, title: 'データ構造の考え方', authors: [{name: '山下四郎' }]})
WriteResult({ "nInserted" : 1 })
> db.books.insert({price: 15000, title: 'スキーマ設計', authors: [{name: '山下四郎' }, {name: '山下五郎'}]})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: sandbox.books index: authors.name_1 dup key: { authors.name: \"山下四郎\" }"
        }
})
```

しかし、以下のようなデータは作ることができる。

```js
> db.books.insert({price: 15000, title: 'スキーマ設計', authors: [{name: '山下五郎' }, {name: '山下五郎'}]})
WriteResult({ "nInserted" : 1 })
> db.books.find()
{ "_id" : ObjectId("5dd7b201b2e406088dafa5c0"), "price" : 12000, "title" : "データ構造の考え方", "authors" : [ { "name" : "山下四郎" } ] }
{ "_id" : ObjectId("5dd7b229b2e406088dafa5c2"), "price" : 15000, "title" : "スキーマ設計", "authors" : [ { "name" : "山下五郎" }, { "name" : "山下五郎" } ] }
```

つまり **この unique index では1つのドキュメントの中で同一の `name` を持つ `author` を複数登録できる** 。
したがって `author` の単位では一意制約を満たしていない。

これを解消して MongoDB で一意制約を設定するには `author` を別コレクションに切り出す必要がある。

# 複数の配列の子要素で unique index

2つ以上の配列の子要素に対して unique index を設定する。

```js
> db.books.createIndex({'authors.name': 1, 'ebooks.format': 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
```

この設定があるコレクションにドキュメントを作ろうとすると以下のようになる。

```js
> db.books.insert({price: 1000, title: 'MongoDB 設計ベストプラクティス', authors: [ {name: '山下六郎'} ], ebooks: [ {format: 'epub'} ]})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 171,
                "errmsg" : "cannot index parallel arrays [ebooks] [authors]"
        }
})
```

**複数の配列フィールドの要素に対して unique index を設定することはできない** 。
1つの配列フィールドの複数の子要素に対しては unique index を設定できる。