$ искать несколько уровней без $ unwind?

У меня есть следующие коллекции

коллекции мест

{ "_id" : ObjectId("5acdb8f65ea63a27c1facf86"), "name" : "ASA College - Manhattan Campus", "addedBy" : ObjectId("5ac8ba3582c2345af70d4658"), "reviews" : [ ObjectId("5acdb8f65ea63a27c1facf8b"), ObjectId("5ad8288ccdd9241781dce698") ] } 

обзоры коллекций

 { "_id" : ObjectId("5acdb8f65ea63a27c1facf8b"), "createdAt" : ISODate("2018-04-07T12:31:49.503Z"), "venue" : ObjectId("5acdb8f65ea63a27c1facf86"), "author" : ObjectId("5ac8ba3582c2345af70d4658"), "content" : "nice place", "comments" : [ ObjectId("5ad87113882d445c5cbc92c8") ], } 

сбор комментариев

 { "_id" : ObjectId("5ad87113882d445c5cbc92c8"), "author" : ObjectId("5ac8ba3582c2345af70d4658"), "comment" : "dcfdsfdcfdsfdcfdsfdcfdsfdcfdsfdcfdsfdcfdsfdcfdsf", "review" : ObjectId("5acdb8f65ea63a27c1facf8b"), "__v" : 0 } 

авторские коллекции

 { "_id" : ObjectId("5ac8ba3582c2345af70d4658"), "firstName" : "Bruce", "lastName" : "Wayne", "email" : "bruce@linkites.com", "followers" : [ObjectId("5ac8b91482c2345af70d4650")] } 

Теперь мой следующий запрос заполнения работает нормально

  const venues = await Venue.findOne({ _id: id.id }) .populate({ path: 'reviews', options: { sort: { createdAt: -1 } }, populate: [ { path: 'author' }, { path: 'comments', populate: [{ path: 'author' }] } ] }) 

Но я хочу достичь этого с помощью $lookup query, но он разбивает место, когда я делаю «$ unwind» для отзывов … Я хочу, чтобы отзывы в одном массиве (например, заполнение) и в том же порядке …

Я хочу получить следующий запрос с $lookup потому что у автора есть поле подписчиков, поэтому мне нужно отправить поле isFollow , выполнив $project который нельзя сделать, используя populate

 $project: { isFollow: { $in: [mongoose.Types.ObjectId(req.user.id), '$followers'] } } 

One Solution collect form web for “$ искать несколько уровней без $ unwind?”

Конечно, есть несколько подходов в зависимости от вашей доступной версии MongoDB. Они различаются от разных способов $lookup through до включения обработки объекта в результате .populate() через .lean() .

Я прошу вас внимательно прочитать разделы, и имейте в виду, что все может быть не так, как кажется при рассмотрении вашего решения по внедрению.

MongoDB 3.6, “вложенный” $ lookup

С MongoDB 3.6 оператор $lookup получает дополнительную возможность включать выражение pipeline а не просто присоединяться к «локальному» к «чужому» значению ключа, что означает, что вы можете по существу сделать каждый $lookup «вложенным» в этот конвейер выражения

 Venue.aggregate([ { "$match": { "_id": mongoose.Types.ObjectId(id.id) } }, { "$lookup": { "from": Review.collection.name, "let": { "reviews": "$reviews" }, "pipeline": [ { "$match": { "$expr": { "$in": [ "$_id", "$$reviews" ] } } }, { "$lookup": { "from": Comment.collection.name, "let": { "comments": "$comments" }, "pipeline": [ { "$match": { "$expr": { "$in": [ "$_id", "$$comments" ] } } }, { "$lookup": { "from": Author.collection.name, "let": { "author": "$author" }, "pipeline": [ { "$match": { "$expr": { "$eq": [ "$_id", "$$author" ] } } }, { "$addFields": { "isFollower": { "$in": [ mongoose.Types.ObjectId(req.user.id), "$followers" ] } }} ], "as": "author" }}, { "$addFields": { "author": { "$arrayElemAt": [ "$author", 0 ] } }} ], "as": "comments" }}, { "$sort": { "createdAt": -1 } } ], "as": "reviews" }}, ]) 

Это может быть очень мощным, как вы видите с точки зрения исходного конвейера, он действительно знает только о добавлении контента в массив "reviews" а затем каждое последующее «nested» выражение конвейера также только когда-либо видит, что это «внутренние» элементы из соединение.

Он является мощным и в некоторых отношениях он может быть немного яснее, так как все пути поля относятся к уровню вложенности, но он запускает ползучесть отступа в структуре BSON, и вам нужно знать, совпадают ли вы с массивами или сингулярные значения при прохождении структуры.

Обратите внимание, что мы также можем делать что-то вроде «сглаживания свойства автора», как видно из записей массива "comments" . Весь целевой результат $lookup может быть «массивом», но внутри «под-конвейера» мы можем преобразовать этот массив из одного элемента в одно значение.

Стандартный поиск по MongoDB $

Сохраняя «соединение на сервере», вы можете сделать это с помощью $lookup , но он просто требует промежуточной обработки. Это длительный подход с деконструированием массива с помощью $unwind и с использованием этапов $group для перестройки массивов:

 Venue.aggregate([ { "$match": { "_id": mongoose.Types.ObjectId(id.id) } }, { "$lookup": { "from": Review.collection.name, "localField": "reviews", "foreignField": "_id", "as": "reviews" }}, { "$unwind": "$reviews" }, { "$lookup": { "from": Comment.collection.name, "localField": "reviews.comments", "foreignField": "_id", "as": "reviews.comments", }}, { "$unwind": "$reviews.comments" }, { "$lookup": { "from": Author.collection.name, "localField": "reviews.comments.author", "foreignField": "_id", "as": "reviews.comments.author" }}, { "$unwind": "$reviews.comments.author" }, { "$addFields": { "reviews.comments.author.isFollower": { "$in": [ mongoose.Types.ObjectId(req.user.id), "$reviews.comments.author.followers" ] } }}, { "$group": { "_id": { "_id": "$_id", "reviewId": "$review._id" }, "name": { "$first": "$name" }, "addedBy": { "$first": "$addedBy" }, "review": { "$first": { "_id": "$review._id", "createdAt": "$review.createdAt", "venue": "$review.venue", "author": "$review.author", "content": "$review.content" } }, "comments": { "$push": "$reviews.comments" } }}, { "$sort": { "_id._id": 1, "review.createdAt": -1 } }, { "$group": { "_id": "$_id._id", "name": { "$first": "$name" }, "addedBy": { "$first": "$addedBy" }, "reviews": { "$push": { "_id": "$review._id", "venue": "$review.venue", "author": "$review.author", "content": "$review.content", "comments": "$comments" } } }} ]) 

Это действительно не так сложно, как вы могли бы подумать вначале, и следует простой шаблон $lookup и $unwind мере продвижения по каждому массиву.

Конечно, "author" деталь является исключительной, поэтому, когда это «разматывается», вы просто хотите оставить ее таким образом, добавьте поле и начните процесс «откат» в массивы.

Восстановить исходный документ места можно только двумя уровнями, поэтому первый уровень детализации – « Review чтобы перестроить массив "comments" . Все, что вам нужно – это $push путь "$reviews.comments" , чтобы собрать их, и до тех пор, пока поле "$reviews._id" находится в «grouping _id», единственное, что вам нужно сохранить все остальные поля. Вы также можете поместить все это в _id , или вы можете использовать $first .

С этим сделано еще один этап в $group , чтобы вернуться к самому Venue . На этот раз ключ группировки – "$_id" конечно, со всеми свойствами самого места проведения, используя $first а остальные "$review" детали возвращаются в массив с $push . Разумеется, вывод "$comments" из предыдущей $group становится "review.comments" .

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

Поскольку данные все еще «подключены к серверу», по- прежнему гораздо меньше трафика, чем другая оставшаяся альтернатива.

Манипуляция JavaScript

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

Проблема, конечно, при использовании populate() заключается в том, что, хотя она может выглядеть « гораздо более упрощенным процессом, она фактически не является ПРИСОЕДИНЕНИЕМ» . Фактически все populate() «скрывают» основной процесс отправки нескольких запросов в базу данных, а затем ожидают результаты с помощью обработки async.

Таким образом, «появление» соединения на самом деле является результатом нескольких запросов к серверу, а затем «манипулирование на стороне клиента» данных для встраивания деталей в массивы.

Таким образом, помимо этого ясного предупреждения о том, что характеристики производительности нигде не близки к тому, чтобы быть наравне с $lookup сервера $lookup , другое предостережение, конечно, заключается в том, что «документы Mongoose» в результате на самом деле не являются объектами JavaScript, подлежащими дальнейшим манипуляциям.

Поэтому для того, чтобы воспользоваться этим подходом, вам нужно добавить метод .lean() к запросу перед выполнением, чтобы .lean() mongoose на возврат «простых объектов JavaScript» вместо типов Document которые отлиты с помощью методов схемы, прикрепленных к модели , Принимая во внимание, конечно, что полученные данные больше не имеют доступа к каким-либо «методам экземпляров», которые в противном случае были бы связаны с самими связанными моделями:

 let venue = await Venue.findOne({ _id: id.id }) .populate({ path: 'reviews', options: { sort: { createdAt: -1 } }, populate: [ { path: 'comments', populate: [{ path: 'author' }] } ] }) .lean(); 

Теперь venue – простой объект, мы можем просто обрабатывать и настраивать по мере необходимости:

 venue.reviews = venue.reviews.map( r => ({ ...r, comments: r.comments.map( c => ({ ...c, author: { ...c.author, isAuthor: c.author.followers.map( f => f.toString() ).indexOf(req.user.id) != -1 } }) ) }) ); 

Таким образом, на самом деле это просто вопрос циклического перемещения по каждой из внутренних массивов до уровня, на котором вы можете увидеть массив followers в деталях author . Затем сравнение может быть выполнено против значений ObjectId хранящихся в этом массиве, после использования с помощью .map() для возврата значений «string» для сравнения с req.user.id который также является строкой (если это не так, add .toString() на этом), так как проще вообще сравнивать эти значения таким образом с помощью кода JavaScript.

Опять же, хотя мне нужно подчеркнуть, что он «выглядит просто», но на самом деле это то, чего вы действительно хотите избежать для производительности системы, поскольку эти дополнительные запросы и передача между сервером и клиентом стоят много времени для обработки и даже из-за накладных расходов на запрос это добавляет реальных затрат на protractorовку между хостинг-провайдерами.


Резюме

Это, в основном, ваши подходы, которые вы можете предпринять, за .populate() «сворачивания своих собственных», где вы фактически выполняете «несколько запросов» к базе данных самостоятельно, вместо использования помощника, который .populate() есть.

Используя заполненный вывод, вы можете просто манипулировать данными в результате, как и любую другую структуру данных, до тех пор, пока вы применяете .lean() к запросу, чтобы преобразовывать или иным образом извлекать данные простого объекта из возвращаемых документов мангуста.

Несмотря на то, что совокупные подходы выглядят гораздо более привлекательными, существует множество преимуществ для выполнения этой работы на сервере. Большие наборы результатов могут быть отсортированы, вычисления могут быть сделаны для дальнейшей фильтрации, и, конечно же, вы получите «единый ответ» на «один запрос», сделанный на сервере, без дополнительных накладных расходов.

Совершенно очевидно, что сами трубопроводы могут быть просто построены на основе атрибутов, уже сохраненных в схеме. Поэтому написать свой собственный метод для выполнения этой «конструкции» на основе прилагаемой схемы не должно быть слишком сложно.

В долгосрочной перспективе, конечно, $lookup – лучшее решение, но вам, вероятно, придется немного поработать над первоначальным кодированием, если, конечно, вы просто не просто копируете из того, что указано здесь;)

  • Как проверить токен регистрации FCM на сервере?
  • Облачные функции Firebase очень медленные
  • Как установить тайм-аут на http.request () в узле?
  • Загрузка файлов с помощью Express 4.0: req.files undefined
  • NodeJS записывает файл изображения base64
  • Нажимайте элементы в массив mongo через mongoose
  • Как разрешить CORS?
  • как указать локальные модули как зависимости пакетов npm
  • FATAL ERROR: CALL_AND_RETRY_LAST Не удалось выполнить выделение - процесс из памяти
  • Node.js getaddrinfo ENOTFOUND
  • CORS: нельзя использовать подстановочный знак в Access-Control-Allow-Origin, когда флаг учетных данных верен
  • Interesting Posts

    Как назначить функцию, возвращающую более одного значения?

    Как восстановить права на запись для USB-устройств?

    CalledFromWrongThreadException: только исходный stream, создавший иерархию представлений, может касаться представлений

    Использование модуля Underscore с Node.js

    Не удается подключиться к локальному серверу MySQL через сокет ‘/var/lib/mysql/mysql.sock’ (2)

    Могу ли я использовать ключ oem windows 8 для активации Windows 8.1?

    Порядок выполнения обработчика события

    Может ли grep показывать только слова, соответствующие шаблону поиска?

    При восстановлении системного образа при попытке доступа к внешним носителям

    Как узнать, какие параметры вывода доступны в моей программе?

    Как создать пользователя SSH, у которого есть разрешение на доступ к определенным папкам в Ubuntu?

    facebook: постоянный токен доступа к странице?

    Удалить и заменить Печатные элементы

    Как подключить javadoc или источники к банкам в папке libs?

    как выделить текст или слово в pdf-файле с помощью iTextsharp?

    Давайте будем гением компьютера.