Chrillewoodz
Chrillewoodz

Reputation: 28328

How to use aggregate to also include the documents that aren't being returned from that method?

Currently I have this function which retrieves all Tag documents used in all Page documents and counts the number of occurrences and attached them to each Tag document returned:

exports.getAll = (req, res, next) => {

  const config = utils.prepareOptions(req.query);

  async.waterfall([
    (done) => {

      Tag.count({}).exec((err, total) => {
        return done(err, total);
      });
    }
  ], (err, total) => {

    Page.aggregate([
      // {
      //   $unwind: "$tags"
      // },
      {
        $group: {
          _id: "$tags", 
          occurrences: {
            $sum: 1
          }
        }
      },
      {
        $lookup: {
          from: "tags", 
          localField: "_id", // this is supposely wrong but I can't prove it
          foreignField: "_id", 
          as: "tagsData" 
        }
      },
      {
        $unwind: "$tagsData"
      },
      { 
        $match: {
          "tagsData.label": new RegExp(config.query, 'i')
        }
      },
      {
        $project: {
          occurrences: "$occurrences",
          tagData: '$tagsData'
        }
      },
      {
        $addFields: {
          "tagData._id": "$_id", 
          "tagData.occurrences": "$occurrences" 
        } 
      }, 
      { 
        $replaceRoot: { 
          newRoot: "$tagData" 
        } 
      },
      {$sort: {[config.sortBy]: config.order}},
      {$limit: config.limit},
      {$skip: config.offset}
      ], (err, tags) => {
        console.log(tags);
        console.log(err);
        console.log(total);
        if (err) {
          return next(err);
        }

        res.json({
          error: null,
          data: tags,
          total: total,
          results: tags.length
        });
      });
  });
};

The problem with this is that a Tag doesn't necessarily have to be used with a Page at any given time. This results in a problem when calling this function because the Tags that aren't used are not returned.

So what I need to do is to also include every Tag that isn't used and set an occurrences: 0 on them.

So if I have 3 Tag documents:

[
  {_id: 1203f8sad9sf, label: 'Tag 1'},
  {_id: 1203asdf89sf, label: 'Tag 2'},
  {_id: 1203asqw89sf, label: 'Tag 3'}
]

And a Page document:

{
  _id: 90182312,
  tags: [ObjectId(1203f8sad9sf), Object(1203asdf89sf)]
}

And Tag 1 and Tag 2 are part of the Page's tags array, how do I also return Tag 3 so that it is included in the tags that is returned from the aggregate method?

Upvotes: 0

Views: 31

Answers (1)

dnickless
dnickless

Reputation: 10918

Given I understand your intention correctly and also assuming that you've got some code in place to make sure that there are no duplicate tag IDs in a page's tags field this query can be rewritten in a substantially more efficient and concise way:

db.getCollection('tags').aggregate([{
    $lookup: {
        from: "page",
        localField: "_id",
        foreignField: "tags",
        as: "pageData"
    }
}, {
    $addFields: {
        "occurrences": {
            $size: "$pageData"
        }
    }
}])

Upvotes: 1

Related Questions