7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MongoDBAdvent Calendar 2019

Day 1

MongoDB の unique index 整理

Last updated at Posted at 2019-11-30

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

まず unique index をセットする。

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

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

> 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 のないドキュメントは作ることができる。

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

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

> 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

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

> 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 の両方が同じデータは作れない
> 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

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

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

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

> 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: \"山下一郎\" }"
        }
})

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

> db.books.createIndex({'author.name.alias': 1}, {unique: true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}
> 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 を設定することもできる。

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

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

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

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

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

配列の子要素で unique index

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

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

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

> 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: \"山下四郎\" }"
        }
})

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

> 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 を設定する。

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

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

> 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 を設定できる。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?