Dave
Dave

Reputation: 334

Filter to not Include Matched Values in Result Arrays

I have a query where I first want to match find the list of matched users and then filter the matches out from the array of external users that was passed in so that I am left with users Id's that have not been matched yet.

Here is a the match Schema:

const mongoose = require('mongoose'); // only match two users at a time.
const Schema = mongoose.Schema;

const MatchSchema = new Schema({
    participants: [{
        type: String, ref: 'user'        
    }],
    blocked: {
        type: Boolean,
        default: false
    }
});

Here is the query with explanations:

db.getCollection('match').aggregate([
               { 
          '$match': {
               '$and': [       
                    { participants: "599f14855e9fcf95d0fe11a7" }, // the current user.
                    { participants: {'$in': [ "598461fcda5afa9e0d2a8a64","598461fcda5afa9e0d111111", "599f14855e9fcf95d0fe5555"] } } // array of external users that I want to check if the current user is matched with.
                ]
            }
        },
        {
            '$project': {
                'participants': 1
            }
        },

This returns the following result:

{
    "_id" : ObjectId("59c0d76e66dd407f5efe7112"),
    "participants" : [ 
        "599f14855e9fcf95d0fe11a7", 
        "599f14855e9fcf95d0fe5555"
    ]
},
{
    "_id" : ObjectId("59c0d76e66dd407f5efe75ac"),
    "participants" : [ 
        "598461fcda5afa9e0d2a8a64", 
        "599f14855e9fcf95d0fe11a7"
    ]
}

what I want to do next it merge the participants array form both results into one array.

Then I want to take away the those matching items from the array of external users so that I am left with user id's that have not been matched yet.

Any help would be much appreciated!

Upvotes: 0

Views: 32

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151112

If you don't want those results in the array, then $filter them out.

Either by building the conditions with $or ( aggregation logical version ):

var currentUser = "599f14855e9fcf95d0fe11a7",
    matchingUsers = [ 
      "598461fcda5afa9e0d2a8a64",
      "598461fcda5afa9e0d111111",
      "599f14855e9fcf95d0fe5555"
    ],
    combined = [currentUser, ...matchingUsers];

db.getCollection('match').aggregate([
  { '$match': {
    'participants': { '$eq': currentUser, '$in': matchingUsers }
  }},
  { '$project': {
    'participants': {
      '$filter': {
        'input': '$participants',
        'as': 'p',
        'cond': {
          '$not': {
            '$or': combined.map(c =>({ '$eq': [ c, '$$p' ] }))
          }
        }
      }
    }
  }}
])

Or use $in ( again the aggregation version ) if you have MongoDB 3.4 which supports it:

db.getCollection('match').aggregate([
  { '$match': {
    'participants': { '$eq': currentUser, '$in': matchingUsers }
  }},
  { '$project': {
    'participants': {
      '$filter': {
        'input': '$participants',
        'as': 'p',
        'cond': {
          '$not': {
            '$in': ['$p', combined ]
          }
        }
      }
    }
  }}
])

It really does not matter. It's just the difference of using JavaScript to build the expression before the pipeline is sent or letting a supported pipeline operator do the array comparison where it is actually supported.

Note you can also write the $match a bit more efficiently by using an "implicit" form of $and, as is shown.

Also note you have a problem in your schema definition ( but not related to this particular query ). You cannot use a "ref" to another collection as String in one collection where it is going to be ObjectId ( the default for _id, and presumed of the hex values obtained ) in the other. This mismatch means .populate() or $lookup functions cannot work. So you really should correct the types.

Unrelated to this. But something you need to fix as a priority.

Upvotes: 1

Related Questions