skip to Main Content

I need to count the number of matching elements between 2 arrays using MongoDB filter aggregation. I tried using $eq and $setIntersection on the condition field of filter but the number of matching elements return just 1 even if all 4 elements match.

Here’s the user scenario:

  1. User enters 4 digit number: 1, 2, 3, 4
  2. It should check the number of matches it has with the numbers on the backend. In this case, 1, 2, 3, 4 also.
  3. Should return 4 matches.

Right now, it’s just returning 1 on my query. If there’s a mismatch, like the user enters 1, 2, 3, 5 then it should return 3 matches.

Here’s the query:

db.collection.aggregate({
  $match: {
    "entries.numbers": {
      $in: [
        1,
        2,
        3,
        4
      ]
    }
  }
},
{
  "$addFields": {
    "matchingCount": {
      $size: {
        $filter: {
          input: "$entries",
          as: "e",
          cond: {
            $eq: [
              "$$e.numbers",
              [
                1,
                2,
                3,
                4
              ]
            ]
          }
        }
      }
    }
  }
})

Here’s the playground link: https://mongoplayground.net/p/5YSDOL6LwGs

2

Answers


  1. 1. Wrt

    but the number of matching elements return just 1 even if all 4 elements match.

    If you remove the $size part from the $addFields stage, you’ll see that matchingCount is actually a list of one object, so $size returns 1.

    "matchingCount": [
      {
        "name": "Josh",
        "numbers": [ 1, 2, 3, 4 ]
      }
    ]
    

    Also, changing the order of [ 1, 2, 3, 4 ] anywhere in the pipeline or the gives no match at all. So you really should just be using sets.

    2. In your other question, you’ve given an example db where all other entries are together in one document, as an array of objects.

    So if you want all the matching entries which have at least one correct guess:

    Input collection, with order of numbers being different from the "winning" [1, 2, 3, 4]:

    [
      {
        "_id": "1234",
        "entries": [
          {"name": "Josh", "numbers": [4, 1, 3, 2]},
          {"name": "only three", "numbers": [1, 2, 3, 5]},
          {"name": "none", "numbers": [9, 8, 7, 6]}  // no matches
        ]
      }
    ]
    

    Query:

    db.collection.aggregate([
      { $unwind: "$entries" },
      {
        // separated for clarity
        $addFields: {
          intersect: {
            $setIntersection: [
              "$entries.numbers",
              [ 1, 2, 3, 4 ]
            ]
          }
        }
      },
      {
        // remove docs which have no match
        $match: {
          intersect: { $ne: [] }
        }
      },
      {
        // can be combined with the stage above
        $addFields: {
          matchingCount: { $size: "$intersect" }
        }
      },
      {
        // if you only want guesses with at least three matches
        $match: {
          $expr: {
            $gte: [
              // change this two whatever you want
              "$matchingCount",
              3
            ]
          }
        }
      },
      {
        $unset: [ "fields", "you", "don't", "want" ]
      }
    ])
    

    Result with my example db:

    [
      {
        "_id": "1234",
        "entries": { "name": "Josh", "numbers": [ 4, 1, 3, 2 ] },
        "intersect": [ 1, 2, 3, 4 ],
        "matchingCount": 4
      },
      {
        "_id": "1234",
        "entries": { "name": "only three", "numbers": [ 3, 5, 1, 2 ] },
        "intersect": [ 1, 2, 3 ],
        "matchingCount": 3
      }
    ]
    

    Mongo Playground

    And here’s a combined-stages version of that, along with
    the playground:

    db.collection.aggregate([
      { $unwind: "$entries" },
      {
        // can be combined with the stage above
        $addFields: {
          matchingCount: {
            $size: {
              $setIntersection: [
                "$entries.numbers",
                [ 1, 2, 3, 4 ]
              ]
            }
          }
        }
      },
      {
        // if you only want guesses with at least three matches
        $match: {
          $expr: {
            $gte: [
              // change this two whatever you want
              "$matchingCount",
              3
            ]
          }
        }
      }
    ])
    
    Login or Signup to reply.
  2. Try this query :

     db.collection.aggregate([
      {
        $match: {
          "entries.numbers": { $in: [1, 2, 3, 4] }
        }
      },
      {
        $addFields: {
          matchingNumbers: {
            $setIntersection: ["$entries.numbers", [1, 2, 3, 4]]
          }
        }
      },
      {
        $addFields: {
          matchingCount: { $size: "$matchingNumbers" }
        }
      }
    ])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search