web-dev-qa-db-ja.com

$ lookup後の集計フィルター

$ lookupの後にフィルターを追加するにはどうすればよいですか?これを行う他の方法はありますか?

私のデータ収集テストは次のとおりです。

{ "_id" : ObjectId("570557d4094a4514fc1291d6"), "id" : 100, "value" : "0", "contain" : [ ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d7"), "id" : 110, "value" : "1", "contain" : [ 100 ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d8"), "id" : 120, "value" : "1", "contain" : [ 100 ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d9"), "id" : 121, "value" : "2", "contain" : [ 100, 120 ] }

Id 100を選択して、子を集約します。

db.test.aggregate([ {
  $match : {
    id: 100
  }
}, {
  $lookup : {
    from : "test",
    localField : "id",
    foreignField : "contain",
    as : "childs"
  }
}]);

私は戻ってきます:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d9"),
      "id":121,
      "value":"2",
      "contain":[ 100, 120 ]
    }
  ]
}

ただし、「値:1」と一致する子のみが必要です。

最後に私はこの結果を期待しています:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    }
  ]
}
20

ここでの質問は実際には異なるものに関するものであり、 $lookup を必要としません。しかし、「$ lookup後のフィルタリング」というタイトルから純粋にここに到着する人にとっては、これらはあなたのためのテクニックです:

MongoDB 3.6-サブパイプライン

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
      "from": "test",
      "let": { "id": "$id" },
      "pipeline": [
        { "$match": {
          "value": "1",
          "$expr": { "$in": [ "$$id", "$contain" ] }
        }}
      ],
      "as": "childs"
    }}
])

以前-$ lookup + $ unwind + $ match合体

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$unwind": "$childs" },
    { "$match": { "childs.value": "1" } },
    { "$group": {
        "_id": "$_id",
        "id": { "$first": "$id" },
        "value": { "$first": "$value" },
        "contain": { "$first": "$contain" },
        "childs": { "$Push": "$childs" }
     }}
])

配列で $unwind を使用するのではなく、なぜ $filter になるのか疑問がある場合は、 Aggregate $ lookup Total size of一致するパイプライン内のドキュメントが最大ドキュメントサイズを超えている これが一般的に必要であり、はるかに最適な理由の詳細について。

MongoDB 3.6以降のリリースの場合、より表現力豊かな「サブパイプライン」は、通常、配列に何かが返される前に、外部コレクションの結果を「フィルター」したいものです。

答えに戻りますが、実際に質問に「参加しない」必要がある理由を説明しています。


元の

このように $lookup を使用することは、ここで望むことを行うための最も「効率的な」方法ではありません。しかし、これについては後で詳しく説明します。

基本的な概念として、結果の配列で $filter を使用するだけです:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$project": {
        "id": 1,
        "value": 1,
        "contain": 1,
        "childs": {
           "$filter": {
               "input": "$childs",
               "as": "child",
               "cond": { "$eq": [ "$$child.value", "1" ] }
           }
        }
    }}
]);

または、代わりに $redact を使用します。

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$redact": {
        "$cond": {
           "if": {
              "$or": [
                { "$eq": [ "$value", "0" ] },
                { "$eq": [ "$value", "1" ] }
              ]
           },
           "then": "$$DESCEND",
           "else": "$$Prune"
        }
    }}
]);

どちらも同じ結果になります。

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    }
  ]
}

要するに、 $lookup 自体は特定のデータのみを選択するクエリを「まだ」実行できません。そのため、すべての「フィルタリング」は $lookup の後に発生する必要があります

しかし、このタイプの「自己結合」では、 $lookup をまったく使用せず、追加の読み取りと「ハッシュマージ」のオーバーヘッドを完全に回避する方が良いでしょう。関連するアイテムを取得し、代わりに $group とするだけです:

db.test.aggregate([
  { "$match": { 
    "$or": [
      { "id": 100 },
      { "contain.0": 100, "value": "1" }
    ]
  }},
  { "$group": {
    "_id": {
      "$cond": {
        "if": { "$eq": [ "$value", "0" ] },
        "then": "$id",
        "else": { "$arrayElemAt": [ "$contain", 0 ] }
      }
    },
    "value": { "$first": { "$literal": "0"} },
    "childs": {
      "$Push": {
        "$cond": {
          "if": { "$ne": [ "$value", "0" ] },
          "then": "$$ROOT",
          "else": null
        }
      }
    }
  }},
  { "$project": {
    "value": 1,
    "childs": {
      "$filter": {
        "input": "$childs",
        "as": "child",
        "cond": { "$ne": [ "$$child", null ] }
      }
    }
  }}
])

私は意図的に余分なフィールドを削除したので、これは少し異なるだけです。本当にしたい場合は、自分で追加してください:

{
  "_id" : 100,
  "value" : "0",
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [ 100 ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [ 100 ]
    }
  ]
}

したがって、ここでの唯一の本当の問題は、現在のドキュメントが $Push への処理中にnullであったときに作成された配列からのparent結果を「フィルタリング」することです=。


ここで欠けていると思われるのは、探している結果に集計や「サブクエリ」がまったく必要ないということです。あなたが結論付けた、またはおそらく他の場所で見つけた構造は、「ノード」とそのすべての「子」を単一の照会要求で取得できるように「設計」されています。

つまり、「クエリ」だけが本当に必要なすべてであり、データコレクション(コンテンツが実際に「縮小」されていないために行われているすべて)は、カーソルの結果を反復する関数にすぎません。

var result = {};

db.test.find({
  "$or": [
    { "id": 100 },
    { "contain.0": 100, "value": "1" }
  ]
}).sort({ "contain.0": 1 }).forEach(function(doc) {
  if ( doc.id == 100 ) {
    result = doc;
    result.childs = []
  } else {
    result.childs.Push(doc)
  }
})

printjson(result);

これはまったく同じことを行います。

{
  "_id" : ObjectId("570557d4094a4514fc1291d6"),
  "id" : 100,
  "value" : "0",
  "contain" : [ ],
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [
              100
      ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [
              100
      ]
    }
  ]
}

そして、ここで本当に必要なのは、「単一」クエリを発行して親と子の両方を選択するだけであるという証拠となります。返されるデータはまったく同じであり、サーバーまたはクライアントで行っていることは、収集された別の形式に「マッサージ」することだけです。

これは、「リレーショナル」データベースでどのように処理したかを考えて「追いつく」ことができるケースの1つであり、データの保存方法が「変更」されているため、使用する必要がないことに気付かない同じアプローチ。

それは、ドキュメントの例のポイント "子参照を含むモデルツリー構造" のポイントです。 1つのクエリ内で親と子を簡単に選択できます。

61
Neil Lunn