Node.js 将$lookup结果合并到现有文档数组中

Node.js 将$lookup结果合并到现有文档数组中,node.js,mongodb,mongoose,aggregation-framework,Node.js,Mongodb,Mongoose,Aggregation Framework,-房间收藏 _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:

-房间收藏

 _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
}
 _id: ObjectId("5c964ae7f5097e3020d1926c");
name: 'John Doe'
-床位收集

 _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
}
 _id: ObjectId("5c964ae7f5097e3020d1926c");
name: 'John Doe'
在decks数组下,是我的租户字段,它有objectId,我将在租户集合中查找这个对象id

-租户收款

 _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
}
 _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"
}
“我如何在第二次查找时添加条件,仅在租户不为null时执行”,这样我就可以检索两个组,或者检索任何相关工作,以便获得所需的结果

我现在真的没有时间进行所有解释(抱歉)

解释 这里的基本问题是使用是您的问题,您不需要它。在生成的数组中使用与
“decks”
数组合并的内容。然后您可以使用
空值

这里要做的是将
“租户”
集合中的值转换到
“床位/床位”
集合中的现有数组中,以用于其自身的现有
“租户”
值,这些值是外部集合的
ObjectId
引用

stage不能简单地将
中的字段路径命名为“
输出,而该路径已经位于另一个数组中,并且实际上的输出始终是从外部集合获得的结果数组。您希望每个实际匹配的值都是单数,当然,您希望在不匹配的地方有一个
null
,当然要保持
“deck”
的原始文档数组完好无损,但只包括找到它们的外部细节

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

真正的解决方案不是“有条件的”,而是将“临时数组”内容从结果转移到现有的
“deck”
条目中。您可以使用来处理数组成员,并使用,以便通过匹配的
\u id
值将匹配的元素从“临时数组”返回到
的“租户”

注意,我们在中使用的是为了保留
“decks”
数组的现有内容,并且仅为每个数组成员替换(或“合并”)覆盖的
“tenant”
表示形式。您已经在使用expressive,这是MongoDB 3.6的一个特性

出于兴趣,同样的事情也可以通过指定数组中的每个字段来完成。i、 e:

            "decks": {
              "$map": {
                "input": "$decks",
                 "in": {
                   "_id": "$$this._id",
                   "number": "$$this.number",
                   "tenant": {
                     // same expression
                   },
                   "__v": "$$this.__v"     // just because it's mongoose
                 }
               }
             }
在中使用的
$$REMOVE
也是MongoDB 3.6的另一个特性。您也可以只使用或省略不需要的字段:

{ "$project": {
  "number": "$number",
  "decks": {
    "$map": { /* same expression */ }
  },
  "__v": "$__v"
  // note we don't use the "tenant" temporary array
}}
但这基本上就是它的工作原理。通过获取结果,然后将这些结果转换回文档中的原始数组

示例列表 同时从前面的问题中提取数据,这比你在这里发布的问题要好一点。用于演示的可运行列表:

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
  }
]

床位
数组中的第二个条目的第二个条目上按预期显示
null

不,没有任何此类情况,但不清楚您期望的是什么,除非您可以显示一些能够产生预期结果的数据,并显示可从该数据中获得的预期结果。这里最可能的问题是
$unwind
,因为
$lookup
在没有匹配的情况下返回空数组。但这只是一个有根据的猜测,没有看到一些数据来重现问题或预期结果。@NeilLunn你能建议一个解决方法,这样我就能达到我想要的结果吗?如果你真的提供一些数据,我可以。屏幕截图不是人们可以使用的数据。文档可以以文本形式查看,因此请在问题中包含“文本”内容,而不是图片。我们实际上可以复制和粘贴文本。查看文档文本表示的最佳方法是使用随MongoDB安装一起安装的
mongo
shell。另外,请阅读我所说的内容,并注意到问题中的代码基本上来自其他人的答案,我们真的希望能够付出一些努力。@NeilLunn经过几个小时的尝试,还没有得到预期的结果,我希望你能帮我解决这个问题。我已经更新了上面的查询。you sirr,是一个向导。我只是想知道你是从哪里学到这些东西的,我很难找到教聚合的好资源,你能分享一些链接吗??