How do I compare multiple collections in the aggregation framework?

I have two collections:

User

var UserSchema = new mongoose.Schema({
    likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Products' }],
    dislikes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Products' }],
})

Product

var ProductSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
})

I would like to return all of the products that is not in the User.likes or User.dislikes. Here is how I am currently doing it:

const user = await User.findById(req.user.id, 'likes dislikes');
let seenProducts = [];

user.likes.forEach(p => {
    seenProducts.push(new mongoose.Types.ObjectId(p));
})

user.dislikes.forEach(p=> {
    seenProducts.push(new mongoose.Types.ObjectId(p));
})

Product.aggregate([
    {
        $match: { _id: { $nin: seenProducts } },
    }
])

It works, but I would like to switch over to using the aggregration pipeline framework to do this if possible. It does not seem like it is easy to compare two collections… I have been trying this for a while with no luck. $setUnion and $setDifference look promising, but I can’t find a way to set the union of the likes & dislikes of the user, and then the difference of that with all products.

Hi Joshua,

You are on the right track. $setUnion will combine likes and dislikes array, but unfortunately $setDifference won’t work across collections. We have to deploy $lookup stage to compare and get all the products that are not in likes or dislikes array. Something like this - https://mongoplayground.net/p/MNl2icwWEsM. Let me know if you have any questions.

users:

{
      likes: [
        ObjectId("6091e8db8fac308fbebd2988"),
        ObjectId("6091e8e18fac308fbebd298a")
      ],
      dislikes: [
        ObjectId("6091e8de8fac308fbebd2989"),
        ObjectId("6091e8e18fac308fbebd298b")
      ]
}

products:

    {
      _id: ObjectId("6091e8e18fac308fbebd211b"),
      title: "hig"
    },
    {
      _id: ObjectId("6091e8db8fac308fbebd2988"),
      title: "abc"
    },
    {
      _id: ObjectId("6091e8e18fac308fbebd298b"),
      title: "xyz"
    },
    {
      _id: ObjectId("6091e8e18fac308fbebd291b"),
      title: "efg"
    }

aggregation:

db.users.aggregate([
  {
    "$set": {
      "likesAndDislikes": {
        "$setUnion": [
          "$likes",
          "$dislikes"
        ]
      }
    }
  },
  {
    "$lookup": {
      "from": "products",
      let: {
        usersChoice: "$likesAndDislikes"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $not: [
                    {
                      $in: [
                        "$_id",
                        "$$usersChoice"
                      ]
                    }
                  ]
                },
                
              ]
            }
          }
        },
        
      ],
      "as": "productsNeitherLikedOrDisliked"
    }
  },
  {
    $project: {
      productsNeitherLikedOrDisliked: 1,
      _id: 0
    }
  }
])
1 Like

Hey, Mahisatya, thank you so much for your reply. Makes sense!