Uche Ozoemena
Uche Ozoemena

Reputation: 926

Find documents with array field that only contains specific values and nothing else

Consider this collection:

{
  { name: 'first', toys: ['doll', 'car', 'doll'],
  { name: 'second', toys: ['doll', 'car'],
  { name: 'third', toys: ['doll', 'car', 'bricks'],
  { name: 'fourth', toys: ['doll', 'bricks'],
  { name: 'fifth', toys: []
}

I want to query for documents whose toys field is an array that only contains doll and car. In this case, both first and second should match. first matches because doll and car can be repeated in the array, third doesn't match because no other value must be present in the array, and fourth and fifth don't match because they don't contain both doll and car.

Using $all and $in doesn't work for me because they match third. How can I achieve this? Thanks!

Upvotes: 3

Views: 4299

Answers (2)

chridam
chridam

Reputation: 103365

A better approach would be to use the aggregate operator $setEquals which compares two or more arrays and returns true if they have the same distinct elements and false otherwise:

db.collection.find({
    '$expr': { 
        '$setEquals': ['$toys', ['doll', 'car']] 
    }
})

Another alternative is $setDifference which takes two arrays and performs a relative complement of the second array relative to the first and this operation does not require elements to be in order.

In your case use the result from $setDifference to check if its empty and set that as basis for your query.

For example the operation

{ $setDifference: [ ['doll', 'car', 'doll'], ['doll', 'car'] ] } => []

and this

{ $setDifference: [ ['car', 'doll', 'doll'], ['doll', 'car'] ] } => []

as well as

{ $setDifference: [ ['car', 'doll'], ['doll', 'car'] ] } => []

or

{ $setDifference: [ ['doll', 'car'], ['doll', 'car'] ] } => []

but

{ $setDifference: [ ['doll', 'car', 'bricks'], ['doll', 'car'] ] } => ['bricks']

Using the logic above, as a helper you can take the length of the array result with $size and check if it's 0 for your query expression with $expr


Final query:

db.collection.find({
    '$expr': {
        '$eq': [
            { '$size': { 
                '$setDifference': [ 
                    '$toys', 
                    ['doll', 'car'] 
                ] 
            } },
            0
        ]
    }
})

Upvotes: 3

Alexpandiyan Chokkan
Alexpandiyan Chokkan

Reputation: 1075

Following query retrieves if the document is having either car or doll or both. But it won't retrieve empty array records.

db.getCollection('toys').find({
    $and: [{
        toys: {$elemMatch: {$in: ["doll", "car"]}}
    },
    {
        toys: {$not: {$elemMatch: {$nin: ["doll", "car"]}}}
    }]})

Upvotes: 2

Related Questions