あなたが正しく言及しているように、それらの実行に固有のさまざまな複雑さを持つさまざまなアプローチがあります。これは基本的に、それらがどのように行われるかをカバーし、実際に実装するものは、データとユースケースが最も適しているかどうかによって異なります。
現在の範囲の一致
MongoDB 3.6 $ lookup
最も単純なアプローチは、の新しい構文を使用して採用できます。 $ lookup
pipeline
を許可するMongoDB3.6の演算子 同じコレクションに「自己参加」する表現として与えられます。これにより、基本的に、 starttime
が存在するすべてのアイテムについてコレクションを再度クエリできます。 "または"endtime
現在のドキュメントの値は、もちろん元のドキュメントを除いて、他のドキュメントと同じ値の間にあります。
db.getCollection('collection').aggregate([
{ "$lookup": {
"from": "collection",
"let": {
"_id": "$_id",
"starttime": "$starttime",
"endtime": "$endtime"
},
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$ne": [ "$$_id", "$_id" },
{ "$or": [
{ "$and": [
{ "$gte": [ "$$starttime", "$starttime" ] },
{ "$lte": [ "$$starttime", "$endtime" ] }
]},
{ "$and": [
{ "$gte": [ "$$endtime", "$starttime" ] },
{ "$lte": [ "$$endtime", "$endtime" ] }
]}
]},
]
},
"as": "overlaps"
}},
{ "$count": "count" },
]
}},
{ "$match": { "overlaps.0": { "$exists": true } } }
])
単一の $ lookup
同じコレクションで「結合」を実行し、 "_ id"
の「現在のドキュメント」の値を保持できるようにします 、 "starttime"
および"endtime"
"let"
を介してそれぞれ値 パイプラインステージのオプション。これらは、 $$
を使用して「ローカル変数」として利用できるようになります 後続の"pipeline"
のプレフィックス 式の。
この「サブパイプライン」内で、 $match<を使用します。 / code>
パイプラインステージと $ expr
クエリ演算子。クエリ条件の一部として集計フレームワークの論理式を評価できます。これにより、条件に一致する新しいドキュメントを選択するときに、値を比較できます。
条件は、 "_ id"
が存在する「処理済みドキュメント」を検索するだけです。 フィールドが「現在のドキュメント」と等しくない、 $ and
ここで、 "starttime"
$ or
"endtime"
「現在のドキュメント」の値は、「処理されたドキュメント」の同じプロパティの間にあります。ここで、これらとそれぞれの $ gte コード>
および $ lte
演算子は、"集計比較演算子"
です。 "クエリ演算子"
ではありません フォーム、 $ expr
>
boolean
である必要があります コンテキストで。これは、集計比較演算子が実際に行うことであり、比較のために値を渡す唯一の方法でもあります。
一致の「カウント」のみが必要なので、 $ count
これを行うには、パイプラインステージが使用されます。全体的な $ lookup
の結果
カウントがあった場合は「単一要素」配列、条件に一致しなかった場合は「空の配列」になります。
別のケースは、 $count<を「省略」することです。 / code>
ステージングし、一致するドキュメントが返されるようにします。これにより簡単に識別できますが、「ドキュメント内に埋め込まれた配列」として、ドキュメント全体として返される「オーバーラップ」の数に注意する必要があります。これにより、BSONの制限である16MBに違反することはありません。ほとんどの場合、これで問題ありませんが、特定のドキュメントで多数のオーバーラップが予想される場合は、これが実際のケースになる可能性があります。ですから、それは本当にもっと知っておくべきことです。
$ lookup
このコンテキストのパイプラインステージは、空の場合でも、結果として「常に」配列を返します。既存のドキュメントに「マージ」する出力プロパティの名前は、 "overlaps"
になります。 "as"
で指定されているとおり $ lookup
のプロパティ
ステージ。
$ lookup
に続く
、その後、簡単な $ match
を実行できます。
$ examples
を使用する通常のクエリ式を使用する
0
をテストします 出力配列のインデックス値。配列に実際にコンテンツがあり、したがって「オーバーラップ」している場合、条件は真になり、ドキュメントが返され、選択に応じてカウントまたはドキュメントが「オーバーラップ」していることが示されます。
その他のバージョン-「参加」するためのクエリ
MongoDBにこのサポートがない別のケースは、調べたドキュメントごとに上記と同じクエリ条件を発行して手動で「参加」することです。
db.getCollection('collection').find().map( d => {
var overlaps = db.getCollection('collection').find({
"_id": { "$ne": d._id },
"$or": [
{ "starttime": { "$gte": d.starttime, "$lte": d.endtime } },
{ "endtime": { "$gte": d.starttime, "$lte": d.endtime } }
]
}).toArray();
return ( overlaps.length !== 0 )
? Object.assign(
d,
{
"overlaps": {
"count": overlaps.length,
"documents": overlaps
}
}
)
: null;
}).filter(e => e != null);
これは基本的に同じロジックですが、重複するドキュメントに一致するクエリを発行するために実際に「データベースに戻る」必要がある点が異なります。今回は、現在のドキュメント値が処理されたドキュメントの値の間にある場所を見つけるために使用される「クエリ演算子」です。
結果はすでにサーバーから返されるため、出力へのコンテンツの追加にBSONの制限はありません。メモリ制限があるかもしれませんが、それは別の問題です。簡単に言えば、 .toArray()
を介してカーソルではなく配列を返します したがって、一致するドキュメントがあり、配列の長さにアクセスしてカウントを取得できます。実際にドキュメントが必要ない場合は、 .count()
.find()
の代わりに ドキュメントフェッチのオーバーヘッドがないため、はるかに効率的です。
次に、出力は既存のドキュメントと単純にマージされます。他の重要な違いは、これらは「複数のクエリ」であるため、何かに「一致」する必要があるという条件を提供する方法がないことです。したがって、これにより、カウント(または配列の長さ)が 0
である結果が生じることを考慮する必要があります。 現時点でできることは、 null
を返すことだけです。 後で.filter()
できる値 結果の配列から。カーソルを反復する他の方法では、結果が不要な場合に結果を「破棄」するという同じ基本原則を採用しています。ただし、サーバーでのクエリの実行を停止するものはなく、このフィルタリングは何らかの形で「後処理」です。
複雑さの軽減
したがって、上記のアプローチは説明した構造で機能しますが、もちろん全体的な複雑さのために、各ドキュメントについて、重複を探すためにコレクション内の他のすべてのドキュメントを本質的に調べる必要があります。したがって、 $ lookup
を使用している間
ある程度の「効率」が可能になります 転送と応答のオーバーヘッドを削減する上で、基本的に各ドキュメントをすべてのものと比較しているのと同じ問題が発生します。
より良い解決策「それを適合させることができる場所」 代わりに、各ドキュメントの間隔を表す「ハード値」*を保存します。たとえば、1日のうち1時間の確実な「予約」期間があり、合計24の予約期間があると「推定」できます。この「可能性」は次のように表されます:
{ "_id": "A", "booking": [ 10, 11, 12 ] }
{ "_id": "B", "booking": [ 12, 13, 14 ] }
{ "_id": "C", "booking": [ 7, 8 ] }
{ "_id": "D", "booking": [ 9, 10, 11 ] }
間隔のインジケーターが設定されているように編成されたデータでは、 "booking"
内の配列からの間隔値を「グループ化」するだけなので、複雑さが大幅に軽減されます。 プロパティ:
db.booking.aggregate([
{ "$unwind": "$booking" },
{ "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
{ "$match": { "docs.1": { "$exists": true } } }
])
そして出力:
{ "_id" : 10, "docs" : [ "A", "D" ] }
{ "_id" : 11, "docs" : [ "A", "D" ] }
{ "_id" : 12, "docs" : [ "A", "B" ] }
これは、 10
のそれを正しく識別します および11
両方の間隔"A"
および"D"
"B"
の間、オーバーラップを含みます および"A"
12
でオーバーラップ 。他の間隔と一致するドキュメントは、同じ $examples<を介して除外されます。 / code>
今回以外は1
でテストします グループ化に「複数の」ドキュメントがあったことを確認するためのインデックス(または2番目の配列要素が存在する)。したがって、重複を示します。
これは、 $ unwind
を使用するだけです。
グループ化のために内部値にアクセスできるように、配列コンテンツを「分解/非正規化」するための集約パイプラインステージ。これはまさに、 $ group
で発生することです。
提供される「キー」が予約間隔IDと $ push
演算子は、そのグループで見つかった現在のドキュメントに関するデータを「収集」するために使用されます。 $ match
前に説明したとおりです。
これは、別のプレゼンテーション用に拡張することもできます:
db.booking.aggregate([
{ "$unwind": "$booking" },
{ "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
{ "$match": { "docs.1": { "$exists": true } } },
{ "$unwind": "$docs" },
{ "$group": {
"_id": "$docs",
"intervals": { "$push": "$_id" }
}}
])
出力あり:
{ "_id" : "B", "intervals" : [ 12 ] }
{ "_id" : "D", "intervals" : [ 10, 11 ] }
{ "_id" : "A", "intervals" : [ 10, 11, 12 ] }
これは単純化されたデモンストレーションですが、必要な種類の分析に使用できるデータがある場合は、これがはるかに効率的なアプローチです。したがって、「粒度」を固定して、各ドキュメントに一般的に記録できる「設定された」間隔に保つことができれば、分析とレポートは後者のアプローチを使用して、そのような重複を迅速かつ効率的に特定できます。
基本的に、これは、基本的に「より良い」アプローチとして言及したものを実装する方法であり、最初は、最初に理論化したものに対する「わずかな」改善です。どちらが実際に状況に合っているかを確認してください。ただし、これで実装と違いが説明されます。