Help us understand the problem. What is going on with this article?

MongoDB の unique index 整理

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした