Объедините две коллекции и затем группы / проект - PullRequest
0 голосов
/ 07 октября 2019

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

запрос, который у меня есть на данный момент, сильно запутан и неоптимизирован, и при этом он не фильтрует моих разрешенных пользователей как таковых.

const cardQuery = [];
      const toolQuery = [];
      const users = [];
      if (req.query.org && (req.user.organization === req.query.org ||
          req.user.roles.includes('SUPER'))) {
        const results = await User.find({organization: req.query.org}).select('_id').lean();
        results.forEach((element) => {
          users.push(element._id);
        });
        cardQuery.push({
          $match: {
            'owner': {$in: users},
          },
        });
      }
toolQuery.push( {
        $project: {
          _id: '$_id',
          title: '$title',
          ratings: '$ratings',
          theme: '$theme',
        },
      });
      toolQuery.push({
        $unwind: {
          'path': '$ratings',
          'preserveNullAndEmptyArrays': true,
        },
      });
      toolQuery.push({
        $project: {
          _id: '$_id',
          title: '$title',
          userId: '$ratings.user',
          rate: {$ifNull: ['$ratings.score', 0]},
          theme: '$theme',
        },
      });
      toolQuery.push({
        $group: {
          _id: '$_id',
          count: {
            $sum: 1,
          },
          avg: {
            $avg: '$rate',
          },
          ratings: {
            $push: {
              rate: '$rate',
              userId: '$userId',
            },
          },
          theme: {
            $first: '$theme',
          },
          title: {
            $first: '$title',
          },
        },
      });
      toolQuery.push({
        $project: {
          _id: '$_id',
          title: '$title',
          ratings: '$ratings',
          theme: '$theme',
          avg: {
            $ifNull: [
              '$avg',
              0,
            ],
          },
          count: {
            $cond: {
              if: {
                $lte: [
                  '$avg',
                  0,
                ],
              },
              then: 0,
              else: '$count',
            },
          },
        },
      });
      toolQuery.push({
        $sort: {
          avg: -1,
          count: -1,
        },
      });
      const toolData = await Tool.aggregate(toolQuery);
      cardQuery.push({$unwind: '$progress'});
      cardQuery.push({
        '$group': {
          '_id': {
            'tool': '$progress.tool',
            'toolcon': {$cond: ['$progress.file_one', 1, 0]},
            'worksheetcon': {$cond: ['$progress.file_two', 1, 0]},
          },
        },
      });
      cardQuery.push({
        '$group': {
          '_id': '$_id.tool',
          'tooldl': {$sum: '$_id.toolcon'},
          'worksheetdl': {$sum: '$_id.worksheetcon'},
        },
      });
      // TODO Why??? We already have the tooldata!
      cardQuery.push({
        $lookup: {
          from: 'tools',
          localField: '_id',
          foreignField: '_id',
          as: 'toolData',
        },
      });
      cardQuery.push({$unwind: '$toolData'});
      cardQuery.push({
        '$project': {
          '_id': '$_id',
          'title': '$toolData.title',
          'tool_downloads': {$sum: '$tooldl'},
          'worksheet_downloads': {$sum: '$worksheetdl'},
        },
      });
      const cardData = await Memorycard.aggregate(cardQuery);
      const formattedData = [];
      for (const tool of toolData) {
        const found = cardData.find((x) => (x._id.toString() === tool._id.toString()));
        if (found) {
          formattedData.push({
            '_id': tool._id,
            'title': tool.title,
            'theme': tool.theme,
            'avg': tool.avg,
            'count': tool.count,
            'ratings': tool.ratings,
            'total_dl': (found.tool_downloads + found.worksheet_downloads),
            'tool_dl': found.tool_downloads,
            'ws_dl': found.worksheet_downloads,
          });
        }
      }

Как вы можете сказать, это довольно громоздко и агрегация не моя любимая вещь. Данные должны напоминать объект formattedData для каждого инструмента в инструментальной базе данных.

Пользовательский документ сохраненного слота

{
    "_id" : ObjectId("5d1c032c2330cc00179ea41e"),
    "owner" : ObjectId("5d1c032c2330cc00179ea41d"),
    "progress" : [ 
        {
            "file_one" : true,
            "file_two" : true,
            "_id" : ObjectId("5d1c03e92330cc00179ea44e"),
            "tool" : ObjectId("5c7d4c9971338741c09c6c73"),
            "createdAt" : ISODate("2019-07-03T01:24:57.677Z"),
            "updatedAt" : ISODate("2019-07-03T01:25:40.165Z")
        }
    ],
}

Документ инструмента

{
    "_id" : ObjectId("5c7d4c9971338741c09c6c65"),
    "title" : "Ecosystem Analysis",
    "rating" : 5,
    "ratings" : [ 
        {
            "_id" : ObjectId("5d23b56deac6ce0017bc8fce"),
            "user" : ObjectId("5d163f1f3bc0ec001701b21c"),
            "score" : 5,
            "feedback" : "Woop woop",
            "username" : "max"
        }, 
        {
            "_id" : ObjectId("5d329d0569b61d0017d801e1"),
            "user" : ObjectId("5d247411eac6ce0017bc91c1"),
            "score" : 5,
            "feedback" : "",
            "username" : "demoaccount"
        }
    ],
    "theme" : "strategy",
    "totalratings" : 2
}

[ПРАВИТЬ]результаты со следующим, но теперь мне нужно отфильтровать идентификаторы владельца, которые не включены во внешний массив.

 // Get all relevant memoryCards from allowed users
      toolQuery.push({
        $lookup: {
          'from': 'memorycards',
          'let': {toolId: '$_id'},

          'pipeline': [
            {'$match': {'$expr': {'$in': ['$$toolId', '$progress.tool']}}},
            {'$unwind': '$progress'},
            {'$match': {'$expr': {'$eq': ['$$toolId', '$progress.tool']}}},
            {
              $project: {
                '_id': 0,
                'byUser': '$owner',
                'tool': '$progress.file_one',
                'worksheet': '$progress.file_two',
              },
            },
          ],
          'as': 'downloads',
        },
      });

[РЕДАКТИРОВАТЬ 2] Конечный результат с некоторыми указаниями из комментариев

toolQuery.push({
        $project: {
          _id: '$_id',
          title: '$title',
          theme: '$theme',
          ratings: '$ratings',
        },
      });
      toolQuery.push({
        $lookup: {
          'from': 'memorycards',
          'let': {toolId: '$_id'},
          'pipeline': [
            {'$match': {'$expr': {'$in': ['$$toolId', '$progress.tool']}}},
            {'$unwind': '$progress'},
            {'$match': {'$expr': {'$eq': ['$$toolId', '$progress.tool']}}},
            {
              $project: {
                '_id': 0,
                'byUser': '$owner',
                'tool': '$progress.file_one',
                'worksheet': '$progress.file_two',
              },
            },
          ],
          'as': 'downloads',
        },
      });
      if (users.length>1) {
      // PRUNE RATINGS
        toolQuery.push({
          $project: {
            'title': 1,
            'theme': 1,
            'avg': {$cond: [{$eq: [{$size: '$ratings'}, 0]}, 0,
              {
                $divide: [{$sum: '$ratings.score'}, {$sum: {$size: '$ratings'}}],
              }],
            },
            'downloads': {
              $filter: {
                input: '$downloads', as: 'download',
                cond: {
                  $gt: [{
                    $size: {$setIntersection: [['$$download.byUser'], users]},
                  }, 0]},
              },
            },
            'ratings': {
              $filter: {
                input: '$ratings', as: 'download',
                cond: {
                  $gt: [{
                    $size: {$setIntersection: [['$$download.user'], users]},
                  }, 0]},
              },
            },
          },
        });
      } else {
        toolQuery.push({
          $project: {
            'title': 1,
            'theme': 1,
            'avg': {$cond: [{$eq: [{$size: '$ratings'}, 0]}, 0,
              {
                $divide: [{$sum: '$ratings.score'}, {$sum: {$size: '$ratings'}}],
              }],
            },
            'downloads': 1,
          },
        });
      }
      toolQuery.push({
        $unwind: '$downloads',
      });
      toolQuery.push({
        $group: {
          _id: '$_id',
          avg: {$first: '$avg'},
          title: {$first: '$title'},
          theme: {$first: '$theme'},
          total_dl: {$sum: {
            $add: [
              {$sum: {$cond: ['$downloads.tool', 1, 0]}},
              {$sum: {$cond: ['$downloads.worksheet', 1, 0]}},
            ],
          }},
        },
      });
      toolQuery.push({
        $sort: {
          avg: -1,
          total_dl: -1,
        },
      });
...