Retrieve empty array or null in mongoDB documents - mongodb

I have a collection with all the students of my school. Each document has a sports array property that lists the sports practiced by each student, but that property may appear either as sports: [] or sports: null or not appear at all.
How can I retrieve all the documents that fall in one of the aforementioned three cases?
How can I add a sport to a student which only has one sport but not expressed as array, i.e. a student that has sports: "Badminton"? Can this property become an array?

You can use the $in operator to query for docs where a field's value is any of a list of target values:
db.students.find({sports: {$in: [null, []]}})
Note that the null case also matches docs where the field isn't present.

I believe you can use $elemMatch for this:
db.students.find({ $not: { $elemMatch: { $exists: true } } })
This tells mongoDB to fail if the array exists and has values. It only returns values that are null or empty.

On supported (later) versions on MongoDB:
find({field:{$not:{$size: 0}}})

Related

How to generate unique id for each element of an array field in MongoDB

How to create a unique ID for each element of an array field, where uniqueness is maintained globally for all documents of the collection?
Is it possible to specify create a unique index for this field?
You can make use of ObjectId data type. ObjectIds are 12-byte values that are guaranteed to be unique across all documents in a collection. You can specify an ObjectId as the value for a field in an array when inserting a new document.
For example, if you have following document:
{
_id: ObjectId("5f9b5a6d65c5f09f7b5a6d65"),
nameOfArrayField: []
}
You can use the following command to insert a new document:
db.collection.insertOne({
nameOfArrayField: [
{
id: new ObjectId(),
name: "Big Cat Public Safety Law"
}
]
});
To specify a unique index, you can use createIndex() method in the MongoDB shell.
db.collection.createIndex({ "nameOfArrayField.id": 1 }, { unique: true })
unique: true option ensures that the id field of the element array will be unique globally for all documents of the collection. It will prevent from inserting the duplicate element with the same id field in the array. Point to be noted that it is an asynchronous operation. You can use the db.collection.getIndexes() method to check if the index is created or not.

Delete documents where value is not an array

It seems that mongodb allows the same syntax for deleting documents where the value is a single value, and whenever the value is present in a collection:
This
db.SomeCollection.deleteMany({UserId: 12345});
Can affect both {UserId: 12345} and {UserId: [12345, 67895, 87897]}.
I understand that this schema isn't ideal; we are using a singular UserId prop to denote both a single id int, and an array of ints. However, I've inherited a database that makes use of mongodb's dynamic nature, if you catch my drift.
What is the safest way to execute a deleteMany query, specifying I only want to delete documents where the value is a single integer?
You can use $type operator to check field type but it's not so obvious in this case. You can't check if UserId is of type double because MongoDB will indicate that array of doubles is a double as well (more here). So you need to invert your logic and check if documents you're trying to remove are not an array (type 4):
db.SomeCollection.deleteMany({ $and: [ { UserId: 12345 }, { UserId: { $not: { $type: 4 } } } ] })

Return Every Certain Object in MongoDB

How to write a query which returns "every" object in the NoSQL database named "address"? ... please note it may be "nested" to other objects.
I tried
.find({
'result.extractorData.data[0].group[0].address': {
$exists: true
}
});
But that didn't work, BTW Data Looks Like:
I think for nested arrays you better to use elemMatch operator
See similar questions here and here
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
More on elemMatch
Also another way you can:
.find({
'result.extractorData.data.group.address': {
$exists: true
}
});

Why is my MongoDb query inserting an embedded document on Update?

This is my MongoDB query:
db.events.update({date:{$gte: ISODate("2014-09-01T00:00:00Z")}},{$set:{"artists.$.soundcloud_toggle":false}},{multi:true,upsert:false})
Apparently I cannot use "artists.$.soundcloud_toggle" to update all artist documents within the artists array:
"The $ operator can update the first array element that matches
multiple query criteria specified with the $elemMatch() operator.
http://docs.mongodb.org/manual/reference/operator/update/positional/"
I'm happy to run the query a number of times changing the index of the array in order to set the soundcloud_toggle property of every artist in every event that matches the query e.g
artists.0.soundcloud_toggle
artists.1.soundcloud_toggle
artists.2.soundcloud_toggle
artists.3.soundcloud_toggle
The problem is: when there is say, only one artist document in the artists array and I run the query with "artists.1.soundcloud_toggle" It will insert an artist document into the artist array with a single property:
{
"soundcloud_toggle" : true
},
(I have declared "upsert:false", which should be false by default anyways)
How do I stop the query from inserting a document and setting soundcloud_toggle:false when there is no existing document there? I only want it to update the property if an artist exists at the given artists array index.
If, like you said, you don't mind completing the operation with multiple queries, you can add an $exists condition to your filter.
E.g. in the 5th iteration, when updating index=4, add: "artists.4": {$exists: true}, like:
db.events.update(
{ date: {$gte: ISODate("2014-09-01T00:00:00Z")},
"artists.4": {$exists: true} },
{ $set:{ "artists.4.soundcloud_toggle" :false } },
{ multi: true, upsert: false }
)

Matching an array field which contains any combination of the provided array in MongoDB

I would like to query with a specified list of array elements such that documents returned can only contain the elements I pass, but need not contain all of them.
Given documents like:
{
name: "Article 1",
tags: ["Funny", "Rad"]
}
{
name: "Article 2",
tags: ["Cool", "Rad"]
}
{
name: "Article 3",
tags: ["Rad"]
}
Here are some example arrays and their respective results.
["Rad"] should return Article 3
["Rad", "Cool"] should return Article 2 and Article 3
["Funny", "Cool"] should return nothing, since there are no articles with only one of those tags or both
I'm sure I can pull this off with $where but I'd like to avoid that for obvious reasons.
You can do this by combining multiple operators:
db.test.find({tags: {$not: {$elemMatch: {$nin: ['Rad', 'Cool']}}}})
The $elemMatch with the $nin is finding the docs where a single tags element is neither 'Rad' nor 'Cool', and then the parent $not inverts the match to return all the docs where that didn't match any elements.
However, this will also return docs where tags is either missing or has no elements. To exclude those you need to add a qualifier that ensures tags has at least one element:
db.test.find({
tags: {$not: {$elemMatch: {$nin: ['Rad', 'Cool']}}},
'tags.0': {$exists: true}
})
The accepted answer works, but isn't optimised. Since this is the top result on Google, here's a better solution.
I went all the way back to version 2.2 in the docs, which is the oldest version available, and all of them state:
If the field holds an array, then the $in operator selects the documents whose field holds an array that contains at least one element that matches a value in the specified array (e.g. <value1>, <value2>, etc.)
Source
So you can just do
db.test.find({tags: {$in: ['Rad', 'Cool']}})
which will return any entries where the tags contain either 'Rad', 'Cool', or both and use an index if available.