Docs Menu
Docs Home
/
データベース マニュアル
/ /

一意のインデックスとスキーマバリデーション

データベースがアプリケーションの設計に準拠しているようにするには、インデックスを戦略的に作成して、インデックスのプロパティとスキーマ検証を組み合わせます。

ユーザーの金銭状況を要約するアプリケーションを考えてみましょう。アプリケーションのメイン ページには、アプリケーションと同期されたユーザーのIDとすべての金融アカウントの残高が表示されます。

アプリケーションはユーザー情報を users というコレクションに保存します。usersコレクションには、次のスキーマを持つドキュメントが含まれています。

db.users.insertOne( {
_id: 1,
name: { first: "john", last: "smith" },
accounts: [
{ balance: 500, bank: "abc", number: "123" },
{ balance: 2500, bank: "universal bank", number: "9029481" }
]
} )

アプリケーションには、次のルールが必要です。

  • ユーザーはアプリケーションに登録し、金融アカウントを同期できません。

  • ユーザーは bank フィールドと number フィールドでアカウントを識別します。

  • 1 つのユーザーが 2 人の異なるユーザーの同じアカウントを登録することはできません。

  • ユーザーは、同じユーザーの同じアカウントを複数回登録することはできません。

ドキュメントをアプリケーションのルールに限定するようにデータベースを設計するには、次の手順を使用してデータベースのユニークインデックスとスキーマ検証を組み合わせます。

1

アプリケーションのルールを適用するには、次の特性を持つ accounts.bank フィールドと accounts.number フィールドにインデックスを作成します。

  • bank フィールドと number フィールドが繰り返されないようにするには、インデックスを一意のものにします。

  • 複数のフィールドのインデックスを作成するには、インデックスを 複合 にします。

  • 配列内のドキュメントのインデックスを作成するには、型のインデックスをマルチキーにします。

したがって、次の仕様とオプションを持つ複合マルチキーユニークインデックスを作成します。

const specification = { "accounts.bank": 1, "accounts.number": 1 };
const options = { name: "Unique Account", unique: true };
db.users.createIndex(specification, options); // Unique Account
2

現在の状態のインデックスは、すべてのドキュメントをインデックス化します。ただし、この実装では、accounts.bank フィールドまたは accounts.number フィールドが欠落しているドキュメントを挿入するとエラーが発生する可能性があります。

例、次のデータを usersコレクションに挿入してみてください。

const user1 = { _id: 1, name: { first: "john", last: "smith" } };
const user2 = { _id: 2, name: { first: "john", last: "appleseed" } };
const account1 = { balance: 500, bank: "abc", number: "123" };
db.users.insertOne(user1);
db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 }
MongoServerError: E11000 duplicate key error collection: test.users index: Unique Account dup key: { accounts.bank: null, accounts.number: null }

指定されたフィールドが 1 つ以上欠落しているドキュメントを インデックス付きコレクションに挿入しようとすると、 MongoDB は次の処理を実行します。

  • 挿入されたドキュメントに欠落しているフィールドを入力します

  • は、その値を に設定します null

  • インデックスにエントリを追加する

accounts.bank フィールドと accounts.number フィールドなしで user1 を挿入すると、 MongoDB はそれらを null に設定し、ユニークインデックスエントリを追加します。user2 など、いずれのフィールドも欠落している後続の挿入では、重複キー エラーが発生します。

これを回避するには、 部分フィルター式を使用して、両方のフィールドを含むドキュメントのみがインデックスに含まれるようにします。詳細については、一意の制約を持つ部分インデックス を参照してください。次のオプションを使用してインデックスを再作成します。

const specification = { "accounts.bank": 1, "accounts.number": 1 };
const optionsV2 = {
name: "Unique Account V2",
partialFilterExpression: {
"accounts.bank": { $exists: true },
"accounts.number": { $exists: true }
},
unique: true
};
db.users.drop( {} ); // Delete previous documents and indexes definitions
db.users.createIndex(specification, optionsV2); // Unique Account V2

accounts.bankaccounts.number フィールドを含まない 2 人のユーザーを挿入して、新しいインデックス定義をテストします。

db.users.insertOne(user1);
db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 }
{ acknowledged: true, insertedId: 2 }
3

2 人の異なるユーザーと同じアカウントを登録できないことを確認するには、次のコードをテストします。

/* Cleaning the collection */
db.users.deleteMany( {} ); // Delete only documents, keep indexes definitions
db.users.insertMany( [user1, user2] );
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user2._id }, { $push: { accounts: account1 } } );
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
MongoServerError: E11000 duplicate key error collection: test.users index: Unique Account V2 dup key: { accounts.bank: "abc", accounts.number: "123" }

2 番目の updateOne コマンドは正しくエラーを返します。これは、2 人の別々のユーザーに同じアカウントを追加することはできないためです。

データベースで同じユーザーに対して同じアカウントを複数回追加することはできないことをテストします。

/* Cleaning the collection */
db.users.deleteMany( {} ); // Delete only documents, keep indexes definitions
db.users.insertMany( [user1, user2] ); // Re-insert test documents
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.findOne( { _id: user1._id } );
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
_id: 1,
name: { first: 'john', last: 'smith' },
accounts: [
{ balance: 500, bank: 'abc', number: '123' },
{ balance: 500, bank: 'abc', number: '123' }
]

返されたコードは、データベースが同じユーザーに同じアカウントを複数回誤って追加することを示しています。このエラーは、 MongoDBインデックスが、同じドキュメントを指す同じキー値を持つ厳密に等しいエントリを重複させないために発生します。

ユーザーに 2 回目に account1 を挿入すると、 MongoDB はインデックスエントリを作成しないため、重複する値はありません。アプリケーション設計を効果的に実装するには、同じユーザーに同じアカウントを複数回追加しようとすると、データベースはエラーを返す必要があります。

4

アプリケーションが同じユーザーに同じアカウントを複数回追加することを拒否するようにするには、スキーマバリデーションを実装します。次のコードでは、$expr 演算子を使用して、配列内の項目が一意であるかどうかを確認するための式を記述します。

const accountsSet = {
$setIntersection: {
$map: {
input: "$accounts",
in: { bank: "$$this.bank", number: "$$this.number" }
}
}
};
const uniqueAccounts = {
$eq: [ { $size: "$accounts" }, { $size: accountsSet } ]
};
const accountsValidator = {
$expr: {
$cond: {
if: { $isArray: "$accounts" },
then: uniqueAccounts,
else: true
}
}
};

{ $isArray: "$accounts" }true の場合、ドキュメントには accounts 配列が存在し、 MongoDB はuniqueAccounts 検証ロジックを適用します。ドキュメントがロジックを渡す場合は有効です。

式は、元の$setIntersection uniqueAccounts配列のサイズとaccounts accountsSetのマッピング バージョンの によって作成されたaccounts のサイズを比較します。

  • $map 関数は、accounts 配列の各エントリを変換して、accounts.bank フィールドと accounts.number フィールドのみを含めます。

  • $setIntersection 関数は、マップされた配列をセットとして扱い重複を削除します。

  • $eq 関数は、元の accounts 配列と排除された accountsSet のサイズを比較します。

両方のサイズが等しく、すべてのエントリが accounts.bankaccounts.number によって一意である場合、検証は true を返します。重複しない場合、重複が存在し、検証はエラーで失敗します。

スキーマ検証をテストして、同じユーザーに同じアカウントを複数回追加することはデータベースで許可されていないことを確認できます。

/* Cleaning the collection */
db.users.drop( {} ); // Delete documents and indexes definitions
db.runCommand( {
collMod: "users", // update collection to use schema validation
validator: accountsValidator
} );
db.users.insertMany( [user1, user2] );
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: 1,
details: {
operatorName: '$expr',
specifiedAs: {
'$expr': {
'$cond': {
if: { '$and': '$accounts' },
then: { '$eq': [ [Object], [Object] ] },
else: true
}
}
},
reason: 'expression did not match',
expressionResult: false
}
}

2 番目の updateOne() コマンドは Document failed validation エラーを返し、同じユーザーに同じアカウントを複数回追加しようとした場合、データベースが拒否されたことを示します。

戻る

クエリの選択性を確保する

ルール バッジを取得する

「インデックスの作成設計の基礎」無料でマスターしましょう!

詳細

項目一覧