クエリは最初に見るほど複雑ではありません。指定された範囲を「オーバーラップ」するすべてのドキュメントを検索するクエリは次のとおりです。
db.test.find( { "startTime" : { "$lt" : new_end_time },
"endTime" : { "$gt": new_start_time }
}
)
これは、開始日が終了日より前で、終了日が開始時刻よりも大きいドキュメントと一致します。範囲を線上の点として視覚化する場合:
-----|*********|----------|****|-----------|******||********|--- s1 e1 s2 e2 s3 e3s4 e4
sX-eXペアは既存の範囲を表します。新しいs5-e5を使用すると、後で始まるペアを削除するとわかります。 終了日(重複することはできません)を実行してから、開始日より前に終了するすべてのペアを削除します。残りがない場合は、挿入することをお勧めします。
その条件は、終了日が$lte
のすべてのドキュメントの結合を行うことです。 開始日と開始日が$gte
の場合 すでに収集されているすべてのドキュメントが含まれます。クエリはこれを反転させて、この条件の反対を満たすドキュメントがないことを確認します。
パフォーマンスの面では、日付を文字列としてのみ保存しているのは残念です。それらをタイムスタンプ(または実際には任意の数)として保存すると、このクエリでインデックスをより有効に活用できるようになります。現状では、パフォーマンスのために、{ "startTime":1, "endTime":1 }
にインデックスを付ける必要があります。 。
挿入する範囲が既存の範囲と重複しているかどうかを確認するのは簡単ですが、2番目の質問:
挿入はクエリを受け取らないため(つまり、条件付きではないため)、挿入を使用してそれを行う適切な方法はありません。
ただし、アップサート条件で更新を使用できます。条件が何にも一致しない場合は挿入できますが、一致する場合は、一致したドキュメントを更新しようとします!
したがって、使用するトリックは、更新をnoopにし、必要なフィールドをアップサートのみに設定することです。 2.4以降、$setOnInsert
があります 更新する演算子。完全なものは次のようになります:
db.test.update(
{ startTime: { "$lt" : new_end_time }, "endTime" : { "$gt": new_start_time } },
{ $setOnInsert:{ startTime:new_start_time, endTime: new_end_time}},
{upsert:1}
)
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("538e0f6e7110dddea4383938")
})
db.test.update(
{ startTime:{ "$lt" : new_end_time }, "endTime" : { "$gt": new_start_time } },
{ $setOnInsert:{ startTime:new_start_time, endTime: new_end_time}},
{upsert:1}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
同じ「更新」を2回実行しました。最初は重複するドキュメントがなかったため、更新によって「アップサート」が実行されました。これは、WriteResult
で確認できます。 戻ってきました。
2回目に実行すると、(もちろんそれ自体が)オーバーラップするため、一致したドキュメントを更新しようとしましたが、実行する作業がないことに気付きました。返されたnMatchedが1であることがわかりますが、何も挿入または変更されていません。