【问题标题】:Merge $lookup result into existing document array将 $lookup 结果合并到现有文档数组中
【发布时间】:2019-04-08 16:43:36
【问题描述】:

-房间收藏

 _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,因为$lookup 在没有匹配项时返回一个空数组。但这只是一个有根据的猜测,没有看到一些数据来重现问题或预期结果。
  • @NeilLunn 你能建议一个解决方法,这样我就可以达到我想要的结果吗??
  • 如果你真的提供一些数据,我可以。屏幕截图不是人们可以使用的数据。可以以文本表示形式查看文档,因此请在您的问题中包含“文本”内容,而不是图片。我们实际上可以复制和粘贴文本。查看文档文本表示的最佳方式是使用随 MongoDB 安装一起安装的 mongo shell。另请阅读我所说的内容,并且不会忘记问题中的代码基本上来自其他人的答案,我们真的希望付出一些努力。
  • @NeilLunn 经过数小时的尝试,没有得到想要的结果,我希望你能帮我解决这个问题。我已经更新了上面的查询。

标签: node.js mongodb mongoose aggregation-framework


【解决方案1】:

现在真的没有时间解释所有内容(对不起)

说明

这里的基本问题是$unwind 的使用是你的问题,你不需要它。在生成的数组内容上使用$map"decks" 数组合并。然后你可以有nulls

您在这里要做的是将$lookup 中的值从您的"tenants" 集合转置 到您的"beds/bedspaces" 集合中的现有数组中,以获取它自己现有的"tenant" 值这是外部集合的ObjectId 引用。

$lookup 阶段不能通过简单地命名"as" 输出中的字段路径来做到这一点,其中该路径已经在另一个数组中,实际上$lookup 的输出总是从外部集合中获得的一系列结果。您希望每个实际匹配的 singular 值,当然您希望 null 在没有任何匹配项的地方出现,当然保持 "decks" 的原始文档数组完整,但只包括发现这些的外国细节。

您的代码尝试似乎部分意识到了这一点,因为您正在将$lookup 结果上的$unwind 用于""tenants" 集合到一个“临时数组” (但您放入现有路径并覆盖内容)然后尝试通过$group$push“重新组合”为数组。但问题当然是$lookup 结果不适用于"decks" 中的每个 数组成员,因此您最终得到的结果比您想要的要少。

真正的解决方案不是“条件$lookup,而是转置结果中的“临时数组”内容到现有的"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" 数组的现有内容,并且仅为每个数组成员替换(或“合并”)"tenant" 的覆盖表示。您已经在使用富有表现力的 $lookup,而 $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 也可以这么说,这也是 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

【讨论】:

  • 你先生,是个巫师。我只是想知道你从哪里学到这些东西,我很难找到教授聚合的好资源,你能分享一些链接吗??
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多