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

时间:2019-04-07 13:04:23

标签: 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: 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
      }
    ]
  }
  ]

还有一个实例,即套牌数组等于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"
}

“我如何在第二次查找中添加条件,仅在租户不为空的情况下执行”,这样我就可以检索到两个卡片组或任何变通方法,从而可以达到我想要的结果

1 个答案:

答案 0 :(得分:1)

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

说明

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

您要在此处进行的操作是将"tenants"集合中的$lookup中的值转置"beds/bedspaces"集合中的现有数组中,因为拥有现有的"tenant"值,它们是外部集合的ObjectId引用。

$lookup阶段不能通过简单地在"as"输出中命名字段路径来做到这一点,其中该路径已经在另一个数组中,并且实际上$lookup的输出是总是从国外收藏中获得的一系列结果。您希望每个实际匹配为奇异值,当然,您希望null放置在没有任何匹配项的位置,并且当然会使"decks"的原始文档数组保持完整,但只包括在国外找到的详细信息。

当您在""tenants"集合的$unwind结果中使用$lookup进入时,您的代码尝试似乎部分意识到了这一点。”临时数组” (但是您将其放入现有路径中并且会覆盖内容),然后尝试通过$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"]}
                            ]
                          }
                        }
                      }
                    }

请注意,我们在$mergeObjects内使用$map是为了保留"decks"数组的现有内容,并仅替换(或“合并”){{ 1}}(每个数组成员)。您已经在使用表现力的$lookup,而$mergeObjects这样的功能是MongoDB 3.6的功能。

仅出于兴趣,只需指定数组中的每个字段即可完成同一操作。即:

"tenant"

对于$addFields中使用的 "decks": { "$map": { "input": "$decks", "in": { "_id": "$$this._id", "number": "$$this.number", "tenant": { // same expression }, "__v": "$$this.__v" // just because it's mongoose } } } 来说也可以这么说,这也是MongoDB 3.6的另一个功能。您也可以只使用$project并忽略不想要的字段:

$$REMOVE

但这基本上就是它的工作方式。通过获取$lookup结果,然后转置这些结果回到文档中的原始数组。

示例列表

还可以从此处先前的问题中提取数据,这比您在此处的问题中发布的内容要好一些。可运行的清单进行演示:

{ "$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