Объединить результат поиска $ в существующий массив документов - PullRequest
0 голосов
/ 07 апреля 2019

-room collection

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

-bedspace collection

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

Есть также случаи, когда массив колоды равен нулю.

В приведенной ниже агрегации будут отображаться только те колоды, которые имеют арендатора с идентификатором объекта, и я хочу отобразить обе колоды.

   {
  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 Ответ

1 голос
/ 07 апреля 2019

На самом деле сейчас нет времени на все объяснения (извините) ,

Объяснение

Основная проблема здесь заключается в том, что использование $unwind - это ваша проблема, и она вам не нужна.Вместо этого используйте $map для содержимого созданного массива, объединяющегося с массивом "decks".Тогда вы можете получить nulls.

То, что вы хотите сделать здесь, это получить значения из $lookup из вашей "tenants" коллекции , транспонированные вСуществующий массив в вашей коллекции "beds/bedspaces" для его собственных существующих значений "tenant", которые являются ObjectId ссылками для внешней коллекции.

Этап $lookup не может сделать это простоименование пути к полю в выводе "as", где этот путь уже находится внутри другого массива, и на самом деле вывод $lookup равен всегда массиву результатов, полученных из стороннегоколлекция.Вам нужно единичные значения для каждого фактического соответствия, и, конечно, вы ожидаете, что null будет на месте, где ничего не совпадает, и, конечно, сохранит исходный массив документов "decks" без изменений, но только с учетомпосторонние сведения о том, где они были найдены.

Ваша попытка кода кажется частично осведомленной об этой точке, когда вы используете $unwind на $lookup результат для коллекции ""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"]}
                            ]
                          }
                        }
                      }
                    }

Заметим, что мы используем $mergeObjects внутри $map, чтобы сохранить существующее содержимое массива "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
                 }
               }
             }

То же самое можно сказать и о $$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
  }
]

Показывает null для второй записи второй записи в массиве bedspaces, как и ожидалось.

...