26

I am querying for finding exact array match and retrieved it successfully but when I try to find out the exact array with values in different order then it get fails.

Example

db.coll.insert({"user":"harsh","hobbies":["1","2","3"]})
db.coll.insert({"user":"kaushik","hobbies":["1","2"]})
db.coll.find({"hobbies":["1","2"]})

2nd Document Retrieved Successfully

db.coll.find({"hobbies":["2","1"]})

Showing Nothing

Please help

7 Answers 7

52

The currently accepted answer does NOT ensure an exact match on your array, just that the size is identical and that the array shares at least one item with the query array.

For example, the query

db.coll.find({ "hobbies": { "$size" : 2, "$in": [ "2", "1", "5", "hamburger" ] }  });

would still return the user kaushik in that case.

What you need to do for an exact match is to combine $size with $all, like so:

db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "1" ] }  });

But be aware that this can be a very expensive operation, depending on your amount and structure of data. Since MongoDB keeps the order of inserted arrays stable, you might fare better with ensuring arrays to be in a sorted order when inserting to the DB, so that you may rely on a static order when querying.

Sign up to request clarification or add additional context in comments.

Comments

19

To match the array field exactly Mongo provides $eq operator which can be operated over an array also like a value.

db.collection.find({ "hobbies": {$eq: [ "singing", "Music" ] }});

Also $eq checks the order in which you specify the elements.

If you use below query:

db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "1" ] }  });

Then the exact match will not be returned. Suppose you query:

db.coll.find({ "hobbies": { "$size" : 2, "$all": [ "2", "2" ] }  });

This query will return all documents having an element 2 and has size 2 (e.g. it will also return the document having hobies :[2,1]).

1 Comment

Hmm you're right, I shouldn't have used the words "exact match" for my query example. Do you know of a solution for querying an exact subset of values (like with $eq) but not checking for order?
2

Mongodb filter by exactly array elements without regard to order or specified order. Source: https://savecode.net/code/javascript/mongodb+filter+by+exactly+array+elements+without+regard+to+order+or+specified+order

// Insert data
db.inventory.insertMany([
   { item: "journal", qty: 25, tags: ["blank", "red"], dim_cm: [ 14, 21 ] },
   { item: "notebook", qty: 50, tags: ["red", "blank"], dim_cm: [ 14, 21 ] },
   { item: "paper", qty: 100, tags: ["red", "blank", "plain"], dim_cm: [ 14, 21 ] },
   { item: "planner", qty: 75, tags: ["blank", "red"], dim_cm: [ 22.85, 30 ] },
   { item: "postcard", qty: 45, tags: ["blue"], dim_cm: [ 10, 15.25 ] }
]);

// Query 1: filter by exactly array elements without regard to order
db.inventory.find({ "tags": { "$size" : 2, "$all": [ "red", "blank" ] }  });
// result:
[
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e02"),
    item: 'journal',
    qty: 25,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e03"),
    item: 'notebook',
    qty: 50,
    tags: [ 'red', 'blank' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e05"),
    item: 'planner',
    qty: 75,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 22.85, 30 ]
  }
]

// Query 2: filter by exactly array elements in the specified order
db.inventory.find( { tags: ["blank", "red"] } )
// result:
[
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e02"),
    item: 'journal',
    qty: 25,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e05"),
    item: 'planner',
    qty: 75,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 22.85, 30 ]
  }
]

// Query 3: filter by an array that contains both the elements without regard to order or other elements in the array
db.inventory.find( { tags: { $all: ["red", "blank"] } } )
// result:
[
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e02"),
    item: 'journal',
    qty: 25,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e03"),
    item: 'notebook',
    qty: 50,
    tags: [ 'red', 'blank' ],
    dim_cm: [ 14, 21 ]
  },
  {
    _id: ObjectId("6179333c97a0f2eeb98a6e05"),
    item: 'planner',
    qty: 75,
    tags: [ 'blank', 'red' ],
    dim_cm: [ 22.85, 30 ]
  }
]

Comments

0

This query will find exact array with any order.

let query = {$or: [
{hobbies:{$eq:["1","2"]}},
{hobbies:{$eq:["2","1"]}}
]};

db.coll.find(query)

5 Comments

This would be a non-ideal solution. Using a combination of $or clauses would require the number of clauses to grow at a rate of n! (n factorial). For even small arrays, this grows very quickly (120 separate clauses for only 5 array elements, and 720 for only 6 array elements!). The older examples here handle this problem far more efficiently, requiring a runtime complexity of at most O(n^2) rather than O(n!).
this is non identical as mentioned in the above comment
@B.Fleming I agree that this might be the non ideal solution. But if you look at the other answers this is the one that works as the question is asked. Could you please provide us with an ideal solution that works?
@YulePale I've added a proper answer to this question that utilizes MongoDB's aggregation framework to ensure an accurate result. Please, for the love of all things good, consider using something like the aggregation framework solution I've outlined if you need a solution for this problem, and avoid procedurally generating an O(n!) array of $or clauses. For convenience's sake, it can be found here: stackoverflow.com/a/63915722/8698101
@B.Fleming I hear you. The only issue is that my code looks for a match and adds one if it does not exist. So I am using the model.update() method. So I guess I'll have to use two queries to avoid using the '$or method'. Thank you for your answer.
0

with $all we can achieve this. Query : {cast:{$all:["James J. Corbett","George Bickel"]}}

Output : cast : ["George Bickel","Emma Carus","George M. Cohan","James J. Corbett"]

Comments

0

Using aggregate this is how I got mine proficient and faster:

 db.collection.aggregate([
 {$unwind: "$array"},
 
  {
        
    $match: {
      
      "array.field" : "value"
      
    }
  },

You can then unwind it again for making it flat array and then do grouping on it.

Comments

0

This question is rather old, but I was pinged because another answer shows that the accepted answer isn't sufficient for arrays containing duplicate values, so let's fix that.

Since we have a fundamental underlying limitation with what queries are capable of doing, we need to avoid these hacky, error-prone array intersections. The best way to check if two arrays contain an identical set of values without performing an explicit count of each value is to sort both of the arrays we want to compare and then compare the sorted versions of those arrays. Since MongoDB does not support an array sort to the best of my knowledge, we will need to rely on aggregation to emulate the behavior we want:

// Note: make sure the target_hobbies array is sorted!
var target_hobbies = [1, 2];

db.coll.aggregate([
  { // Limits the initial pipeline size to only possible candidates.
    $match: {
      hobbies: {
        $size: target_hobbies.length,
        $all: target_hobbies
      }
    }
  },
  { // Split the hobbies array into individual array elements.
    $unwind: "$hobbies"
  },
  { // Sort the elements into ascending order (do 'hobbies: -1' for descending).
    $sort: {
      _id: 1,
      hobbies: 1
    }
  },
  { // Insert all of the elements back into their respective arrays.
    $group: {
      _id: "$_id",
      __MY_ROOT: { $first: "$$ROOT" }, // Aids in preserving the other fields.
      hobbies: {
        $push: "$hobbies"
      }
    }
  },
  { // Replaces the root document in the pipeline with the original stored in __MY_ROOT, with the sorted hobbies array applied on top of it.
    // Not strictly necessary, but helpful to have available if desired and much easier than a bunch of 'fieldName: {$first: "$fieldName"}' entries in our $group operation.
    $replaceRoot: {
      newRoot: {
        $mergeObjects: [
          "$__MY_ROOT",
          {
            hobbies: "$hobbies"
          }
        ]
      }
    }
  }
  { // Now that the pipeline contains documents with hobbies arrays in ascending sort order, we can simply perform an exact match using the sorted target_hobbies.
    $match: {
      hobbies: target_hobbies
    }
  }
]);

I cannot speak for the performance of this query, and it may very well cause the pipeline to become too large if there are too many initial candidate documents. If you're working with large data sets, then once again, do as the currently accepted answer states and insert array elements in sorted order. By doing so you can perform static array matches, which will be far more efficient since they can be properly indexed and will not be limited by the pipeline size limitation of the aggregation framework. But for a stopgap, this should ensure a greater level of accuracy.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.