簡體   English   中英

將$ lookup結果合並到現有文檔數組中

[英]Merge $lookup result into existing document array

房間集合

 _id: ObjectId("xxx")
 bedspaces: Array
  0:ObjectId("xx")
  1:ObjectId("xx")
 ***
 ***

-床位集合

_id: ObjectId("xxxx");
number: 1
decks: Array
{
 _id: ObjectId("xxx");
 number: 1
 status: "Vacant"
 tenant: ObjectId("5c964ae7f5097e3020d1926c")
 dueRent: 11
 away: null
},
{
 _id: ObjectId("xxx");
 number: 2
 status: "Vacant"
 tenant: null
 dueRent: 11
 away: null
}

在decks數組下,是我的租戶字段,它具有objectId,我將在租戶集合中查找此對象ID。

租戶集合

 _id: ObjectId("5c964ae7f5097e3020d1926c");
name: 'John Doe'

預期的輸出

/*room collection*/
_id: ObjectId("xxx")
bedspaces: [
  {
    _id: ObjectId("xxx")
    number: 1
    decks: [
      {
        _id: ObjectId("xxx")
        number: 1
        status: "Vacant"
        tenant: {
         name: 'John Doe'
        }
        dueRent: 11
        away: null
      },
      {
        _id: ObjectId("xxx");
        number: 1
        status: "Vacant"
        tenant: null
        dueRent: 11
        away: null
      }
    ]
  }
  ]

還有一個實例,即deck數組等於null。

在下面的聚合中,它將僅顯示具有對象ID的承租人的甲板,我要顯示的是兩個甲板。

   {
  from: 'beds',
  let: {bedspace: '$bedspaces'},
  pipeline:[
    {
      $match: {
        $expr: {
          $in: ["$_id", "$$bedspace"]
        }
      }
    },
    {
       $unwind: "$decks"
    },
  {
  $lookup: {
    from: 'tenants',
    let: {tenant: "$decks.tenant"},
    pipeline: [
    {
      $match: {
        $expr: {
          $eq: ["$_id", "$$tenant"]
        }
      } 


 }
    ],
    as: "decks.tenant",
  }
},
{
  $unwind: "$decks.tenant"
},
 { $group: {
        _id: "$_id",
        decks: { $push: "$decks" },
        number: {$first: "$number"}
      }}

  ],
  as: "bedspaces"
}

“如何在第二次查找中添加條件,僅在租戶不為空的情況下執行”,這樣我就可以檢索兩個卡片組或任何替代方法,從而可以實現所需的結果

現在真的沒有時間進行所有解釋(抱歉)

說明

這里的基本問題是$unwind是您的問題,您不需要它。 在產生的數組內容上使用$map ,將其與"decks"數組合並。 然后您可以有nulls

您要在此處執行的操作是將"tenants"集合中$lookup的值轉換"beds/bedspaces"集合中的現有數組,因為它本身是現有的"tenant"值,這些值是外部集合的ObjectId引用。

$lookup階段不能通過簡單地在"as"輸出中命名字段路徑來做到這一點,而該路徑已經在另一個數組中,並且實際上$lookup的輸出始終是從外部集合獲得的結果數組。 您需要為每個實際匹配使用奇異值,當然,您希望在沒有任何匹配項的地方有一個null值,並且當然要使"decks"的原始文檔數組保持完整,但只包括找到這些位置的外部細節。

您的代碼嘗試似乎部分地意識到了這一點,因為您正在對""tenants"集合中的$lookup結果使用$unwind進入“臨時數組” (但您將其放入現有路徑中並覆蓋了內容),然后嘗試通過$group$push來“重新分組”為數組,但是當然問題是$lookup結果並不適用於"decks" 每個數組成員,因此最終得到的結果少於所需的結果。

真正的解決方案不是“ conditional $lookup ,而是將結果中的“ temporary array”內容轉置為現有的"decks"條目。 你做到這一點使用$map處理數組成員,和$arrayElemAt沿$indexOfArray為了通過匹配從“臨時數組”返回匹配的元素_id"tenant"

          { "$lookup": {
            "from": Tenant.collection.name,
            "let": { "tenant": "$decks.tenant" },
            "pipeline": [
              { "$match": {
                "$expr": { "$in": [ "$_id", "$$tenant" ] }
              }}
            ],
            "as": "tenant"
          }},
          { "$addFields": {
            "decks": {
              "$map": {
                "input": "$decks",
                "in": {
                  "$mergeObjects": [
                    "$$this",
                    {
                      "tenant": {
                        "$cond": {
                          "if": {
                            "$eq": [
                              { "$indexOfArray": ["$tenant._id", "$$this.tenant"] },
                              -1
                            ]
                          },
                          "then": null,
                          "else": {

                            "$arrayElemAt": [
                              "$tenant",
                              { "$indexOfArray": ["$tenant._id", "$$this.tenant"]}
                            ]
                          }
                        }
                      }
                    }

請注意,我們在$map中使用$mergeObjects ,以保留"decks"數組的現有內容,並僅替換(或“ merge”)每個數組成員的"tenant"的覆蓋表示。 您已經在使用富有表現力的$lookup而像$mergeObjects這樣的$mergeObjects就是MongoDB 3.6的功能。

只是出於興趣,只需指定數組中的每個字段即可完成同一件事。 即:

            "decks": {
              "$map": {
                "input": "$decks",
                 "in": {
                   "_id": "$$this._id",
                   "number": "$$this.number",
                   "tenant": {
                     // same expression
                   },
                   "__v": "$$this.__v"     // just because it's mongoose
                 }
               }
             }

對於$addFields使用的$$REMOVE可以說$addFields ,這也是MongoDB 3.6的另一個功能。 您也可以只使用$project而忽略不需要的字段:

{ "$project": {
  "number": "$number",
  "decks": {
    "$map": { /* same expression */ }
  },
  "__v": "$__v"
  // note we don't use the "tenant" temporary array
}}

但這基本上就是它的工作方式。 通過獲取$lookup結果,然后這些結果回到文檔中的原始數組中。

示例清單

還要從此處以前的問題中提取數據,這比您在此處的問題中發布的內容要好一些。 可運行的清單進行演示:

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/hotel';
const opts = { useNewUrlParser: true };

mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndexes', true);
mongoose.set('debug', true);

const tenantSchema = new Schema({
  name: String,
  age: Number
});

const deckSchema = new Schema({
  number: Number,
  tenant: { type: Schema.Types.ObjectId, ref: 'Tenant' }
});

const bedSchema = new Schema({
  number: Number,
  decks: [deckSchema]
});

const roomSchema = new Schema({
  bedspaces: [{ type: Schema.Types.ObjectId, ref: 'Bed' }]
});


const Tenant = mongoose.model('Tenant', tenantSchema);
const Bed = mongoose.model('Bed', bedSchema);
const Room = mongoose.model('Room', roomSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // Clean data
    await Promise.all(
      Object.entries(conn.models).map(([k, m]) => m.deleteMany())
    );

    // Insert data
    let [john, jane, bilbo ] = await Tenant.insertMany([
      {
        _id: ObjectId("5c964ae7f5097e3020d1926c"),
        name: "john doe",
        age: 11
      },
      {
        _id: ObjectId("5c964b2531bc162fdce64f15"),
        name: "jane doe",
        age: 12
      },
      {
        _id: ObjectId("5caa5454494558d863513b24"),
        name: "bilbo",
        age: 111
      }
    ]);

    let bedspaces = await Bed.insertMany([
      {
        _id: ObjectId("5c98d89c6bd5fc26a4c2851b"),
        number: 1,
        decks: [
          {
            number: 1,
            tenant: john
          },
          {
            number: 1,
            tenant: jane
          }
        ]
      },
      {
        _id: ObjectId("5c98d89f6bd5fc26a4c28522"),
        number: 2,
        decks: [
          {
            number: 2,
            tenant: bilbo
          },
          {
            number: 3
          }
        ]
      }
    ]);

    await Room.create({ bedspaces });

    // Aggregate

    let results = await Room.aggregate([
      { "$lookup": {
        "from": Bed.collection.name,
        "let": { "bedspaces": "$bedspaces" },
        "pipeline": [
          { "$match": {
            "$expr": { "$in": [ "$_id", "$$bedspaces" ] }
          }},
          { "$lookup": {
            "from": Tenant.collection.name,
            "let": { "tenant": "$decks.tenant" },
            "pipeline": [
              { "$match": {
                "$expr": { "$in": [ "$_id", "$$tenant" ] }
              }}
            ],
            "as": "tenant"
          }},
          { "$addFields": {
            "decks": {
              "$map": {
                "input": "$decks",
                "in": {
                  "$mergeObjects": [
                    "$$this",
                    {
                      "tenant": {
                        "$cond": {
                          "if": {
                            "$eq": [
                              { "$indexOfArray": ["$tenant._id", "$$this.tenant"] },
                              -1
                            ]
                          },
                          "then": null,
                          "else": {

                            "$arrayElemAt": [
                              "$tenant",
                              { "$indexOfArray": ["$tenant._id", "$$this.tenant"]}
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            },
            "tenant": "$$REMOVE"
          }}
        ],
        "as": "bedspaces"
      }}
    ]);

    log(results);

  } catch (e) {
    console.error(e)
  } finally {
    mongoose.disconnect();
  }


})()

返回:

Mongoose: tenants.deleteMany({}, {})
Mongoose: beds.deleteMany({}, {})
Mongoose: rooms.deleteMany({}, {})
Mongoose: tenants.insertMany([ { _id: 5c964ae7f5097e3020d1926c, name: 'john doe', age: 11, __v: 0 }, { _id: 5c964b2531bc162fdce64f15, name: 'jane doe', age: 12, __v: 0 }, { _id: 5caa5454494558d863513b24, name: 'bilbo', age: 111, __v: 0 } ], {})
Mongoose: beds.insertMany([ { _id: 5c98d89c6bd5fc26a4c2851b, number: 1, decks: [ { _id: 5caa5af6ed3dce1c3ed72cef, number: 1, tenant: 5c964ae7f5097e3020d1926c }, { _id: 5caa5af6ed3dce1c3ed72cee, number: 1, tenant: 5c964b2531bc162fdce64f15 } ], __v: 0 }, { _id: 5c98d89f6bd5fc26a4c28522, number: 2, decks: [ { _id: 5caa5af6ed3dce1c3ed72cf2, number: 2, tenant: 5caa5454494558d863513b24 }, { _id: 5caa5af6ed3dce1c3ed72cf1, number: 3 } ], __v: 0 } ], {})
Mongoose: rooms.insertOne({ bedspaces: [ ObjectId("5c98d89c6bd5fc26a4c2851b"), ObjectId("5c98d89f6bd5fc26a4c28522") ], _id: ObjectId("5caa5af6ed3dce1c3ed72cf3"), __v: 0 })
Mongoose: rooms.aggregate([ { '$lookup': { from: 'beds', let: { bedspaces: '$bedspaces' }, pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$bedspaces' ] } } }, { '$lookup': { from: 'tenants', let: { tenant: '$decks.tenant' }, pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$tenant' ] } } } ], as: 'tenant' } }, { '$addFields': { decks: { '$map': { input: '$decks', in: { '$mergeObjects': [ '$$this', { tenant: [Object] } ] } } }, tenant: '$$REMOVE' } } ], as: 'bedspaces' } } ], {})
[
  {
    "_id": "5caa5af6ed3dce1c3ed72cf3",
    "bedspaces": [
      {
        "_id": "5c98d89c6bd5fc26a4c2851b",
        "number": 1,
        "decks": [
          {
            "_id": "5caa5af6ed3dce1c3ed72cef",
            "number": 1,
            "tenant": {
              "_id": "5c964ae7f5097e3020d1926c",
              "name": "john doe",
              "age": 11,
              "__v": 0
            }
          },
          {
            "_id": "5caa5af6ed3dce1c3ed72cee",
            "number": 1,
            "tenant": {
              "_id": "5c964b2531bc162fdce64f15",
              "name": "jane doe",
              "age": 12,
              "__v": 0
            }
          }
        ],
        "__v": 0
      },
      {
        "_id": "5c98d89f6bd5fc26a4c28522",
        "number": 2,
        "decks": [
          {
            "_id": "5caa5af6ed3dce1c3ed72cf2",
            "number": 2,
            "tenant": {
              "_id": "5caa5454494558d863513b24",
              "name": "bilbo",
              "age": 111,
              "__v": 0
            }
          },
          {
            "_id": "5caa5af6ed3dce1c3ed72cf1",
            "number": 3,
            "tenant": null
          }
        ],
        "__v": 0
      }
    ],
    "__v": 0
  }
]

按預期在bedspaces數組中第二個條目的第二個條目上顯示null

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM