Mongoose / MongoDB выполняют где после заселения - PullRequest
4 голосов
/ 14 октября 2019

В настоящее время я сталкиваюсь с проблемой, когда я хочу получить списки из MongoDB, используя mongoose, которая ранее не нравилась пользователю.

Таблица списков:

const listingSchema = new Schema({
   _id,
   ...etc
})
listingSchema.virtual('listingLikes',{
    ref:'ListingLikes',
    ...etc
})

Таблица списков лайков:

const listingLikesSchema = new Schema({
   _id,
   listingId,
   userId
})

Я хочу иметь возможность выполнять такой запрос:

ListingModel.find().populate('listingLikes').where({'listingLikes.userId':{$ne:userId}}).limit(10).exec()

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

Listing.find().where({_id:{$nin:[ ...All previously liked user listings in the listing likes model ) ]}})

Однако я считаю, что это в значительной степени неэффективно (необходимо загружать все ранее понравившиеся списки пользователей в память, прежде чем находить списки).

Как можноЯ делаю это более эффективно? в идеале без изменения текущей схемы.

Ответы [ 2 ]

4 голосов
/ 15 октября 2019

Если вы хотите ввести userId для исключения в качестве переменной, aggregate() с $lookup, скорее всего, сработает:

let loggedInUser = 'Eladian'
ListingModel.aggregate([
  {
    // $lookup is instead of .populate() but does roughly the same thing,
    // bringing in the data from the listingLikes table
    '$lookup': {
      'from': 'listingLikesModel', 
      'localField': 'listingLikes', 
      'foreignField': '_id', 
      'as': 'listingLikes'
    }
  }, {
    // $lookup puts its results in an array in your table. 
    // We flatten it using $unwind
    '$unwind': '$listingLikes'
  }, {
    // $match is equivalent to 'where' 
    '$match': {
      'listingLikes.userId': {
        '$ne': loggedInUser
      }
    }
  }, {
    // limit to 10 results
    '$limit': 10
  }
])

В качестве альтернативы, вы можете добавить $ конвейер к$ lookup, который, как я считаю, будет более эффективным и позволит вам ввести userId из ListingModel или из любого другого места, где он может храниться:

ListingModel.aggregate([
  {
    '$lookup': {
      'from': 'listingLikesModel',
      // below assumes there is a 'userId' field in ListingModel,
      // which we put in to a variable (listingUserId) so we can use it in
      // the pipeline
      'let': {'listingUserId': '$userId'}, 
      'pipeline': [
        {
          '$match': {
            '$expr': {
              '$ne': [ '$userId', '$$listingUserId']
            }
          }
        },
        { '$limit': 10 }
      ],
      // It puts the 10 posts in an array called 'notLikedList' in the ListingModel
      'as': 'notLikedList'
    }
  }
])

Подробнее о конвейерах $ lookup здесь

0 голосов
/ 18 октября 2019

Эффективность Wrt cursor (который является указателем на набор результатов запроса) может использоваться для извлечения данных результатов запроса один за другим в памятьи фильтр:

Примечание: В соответствии со схемой listingLikes сохранена фактическая ссылка Listings (_id как listingId), которая предлагает настроить listingSchema для непосредственного определения listingLikes как поля с помощью ref. И не иметь его как virtuals.

Поскольку следующее решение в соответствии с вышеизложенным. Следует отметить, что match нельзя использовать непосредственно для заполнения virtuals.

Идея заключается в следующем:

  • Подготовьте запрос для заполнения listingLikes для Listings соответствием userId
  • Переберите курсор этого запроса и нажмите документы, где listingLikes [] or null пусто / пусто, так как они будут в спискахэтот пользователь не любил.

Поскольку cursors реализует AsyncIterator interface, что позволяет использовать его в for…await циклах.

let requiredListingDocs = [];
//with cursor limit can be removed if it was there to bring only some documents in memory
const cursor = ListingModel.find({})
  .populate({
    path: "listingLikes",
    match: { userId: userId },
    options: { limit: 10 }
  })
  .cursor();
//will contain listingLikes [] empty/or null for Listing docs that user didn't like

//looping on cursor which returns promise
for await (const doc of cursor) {
    if (doc["listingLikes"] === null || doc["listingLikes"].length === 0) {
        requiredListingDocs.push(doc);
    }
  }
console.info("LISTING DATA::", requiredListingDocs);
...