Search for empty arrays in mongodb atlas - mongodb-atlas

I needed some help with the atlas search aggregation query.
I want to use $search syntax while doing the atlas search :
I have one collection; inside which I have one array field of ObjectIds like so :
arrayFieldOfObjectIds : [ObjectId(62ff26c349a3c47656765434), ObjectId(62ff26c349a3c47656765435)]
Now the array fields can also be empty in some cases like so :
arrayFieldOfObjectIds : []
I have defined the correct index mapping for this field.
My goal is to get all the documents that met the below conditions:
1. Either arrayFieldOfObjectIds doesn't exist.
2. If arrayFieldOfObjectIds does exist, it must be empty.
3. If arrayFieldOfObjectIds does exist, it must be equal to some specified value.
The query I have for arrayFieldOfObjectIds is :
{
"compound": {
"should": [
{
"compound": {
"mustNot": [
{
"exists": {
"path": "arrayFieldOfObjectIds1"
}
}
]
}
},
{
"equals": {
"value": ObjectId("62ff26c349a3c47656765434"),
"path": "arrayFieldOfObjectIds1"
}
}
],
"minimumShouldMatch": 1
}
}
This query doesn't give me those documents where arrayFieldOfObjectIds does exist and is empty.
Note: I need to use $search syntax and I also don't want to combine $match with $search as it kills the query performance altogether.
Thanks in advance.

Related

Delete matching two regular expressions on the same field

I need to delete documents but with multiple condition at the same variable.
db.getCollection('system_parameter').remove(
{
"variable":/^pickup_kota/,
"variable":
{
$nin:
[
/^pickup_kota_jakarta/
]
}
}
)
What I'm trying to do is, I want to delete all the data with the same prefix ('pickup_kota'), but excluding the ('pickup_kota_jakarta') documents.
If I execute the query above, ALL of the data is removed including let's say prefix 'some_doc' but excluding 'pickup_kota_jakarta'
All MongoDB query arguments are already AND conditions, so just include them on the same key:
db.getCollection('system_parameter').remove({
"variable": {
"$regex": /^pickup_kota/,
"$nin": [
/^pickup_kota_jakarta/
]
}
})
Or you can always write the "long form" with $and
db.getCollection('system_parameter').remove({
"$and": [
{ "variable": /^pickup_kota/ },
{ "variable": "$nin": [/^pickup_kota_jakarta/] }
]
})
So with two documents like this:
{ "variable" : "pickup_kota_somewhere" }
{ "variable" : "pickup_kota_jakarta" }
Only the first one gets removed
But as long as you can use a different operator such as $regex here to separate the conditions onto keys, then you don't need the full form.
Also since those are both anchored to the start of the string, it's more efficient for MongoDB to do the two comparisons than attempting a regular expression to meet both possible conditions. The only regular expression that could would break that bounding rule, and obviate the efficiency gained by searching anchored to the beginning of the string which can use an index.
You can try $all operator.
db.getCollection('system_parameter').remove({
"variable": {$all: [
{$regex: /^pickup_kota/},
{$nin:[/^pickup_kota_jakarta/]}
]}
})

Mongodb weird behaviour of $exists

I don't understand the behaviour of the command $exists.
I have two simple documents in the collection 'user':
/* 1 */
{
"_id" : ObjectId("59788c2f6be212c210c73233"),
"user" : "google"
}
/* 2 */
{
"_id" : ObjectId("597899a80915995e50528a99"),
"user" : "werty",
"extra" : "very important"
}
I want to retrieve documents which contain the field "extra" and the value is not equal to 'unimportant':
The query:
db.getCollection('users').find(
{"extra":{$exists:true},"extra": {$ne:"unimportant"}}
)
returns both two documents.
Also the query
db.getCollection('users').find(
{"extra":{$exists:false},"extra": {$ne:"unimportant"}}
)
returns both two documents.
It seems that $exists (when used with another condition on the same field) works like an 'OR'.
What I'm doing wrong? Any help appreciated.
I used mongodb 3.2.6 and 3.4.9
I have seen Mongo $exists query does not return correct documents
but i haven't sparse indexes.
Per MongoDB documentation (https://docs.mongodb.com/manual/reference/operator/query/and/):
Using an explicit AND with the $and operator is necessary when the same field or operator has to be specified in multiple expressions.
Therefore, and in order to enforce the cumpliment of both clauses, you should use the $and operator like follows:
db.getCollection('users').find({ $and : [ { "extra": { $exists : true } }, { "extra" : { $ne : "unimportant" } } ] });
The way you constructed your query is wrong, nothing to do with how $exists works. Because you are checking two conditions, you would need a query that does a logical AND operation to satisfy the two conditions.
The correct syntax for the query
I want to retrieve documents which contain the field "extra" and the
value is not equal to 'unimportant'
should follow:
db.getCollection('users').find(
{
"extra": {
"$exists": true,
"$ne": "unimportant"
}
}
)
or using the $and operator as:
db.getCollection('users').find(
{
"$and": [
{ "extra": { "$exists": true } },
{ "extra": { "$ne": "unimportant" } }
]
}
)

mongodb query to verify embedded array sequence numbers

given a document structure as shown, where the trades array can have thousands of items... how on earth could one do a query that would verify that the sequence always has 'startTradeId' one number higher than the previous items 'endTradeId', all the way through the array? is this even possible?
{
"name": "STOCK",
"trades": [{
"endTradeId": 41306,
"startTradeId": 41302,
...
},
{
"endTradeId": 41301,
"startTradeId": 41297,
...
},
{
"endTradeId": 41296,
"startTradeId": 41240,
...
},
...
]
}
You can use $where operator like below :
db.your_collection.find( { $where : function(){ return "this.trades.startTradeId > this.trades.endTradeId" }});

Does MongoDB's $elemMatch projection guarantee the returned element is the same one that was matched in the query?

Let's say I have a collection with this single document:
{
"_id" : ObjectId("…"),
"cartId" : "61",
"items" : [
{
"prodType" : "hardware",
"prod" : "screwdriver",
"checked": false
},
{
"prodType" : "hardware",
"prod" : "hammer",
"checked": false
},
{
"prodType" : "decor",
"prod" : "vase",
"checked": false
}
]
}
And I want to do findAndModify to find any hardware product and modify its checked field. Then it will look like this:
db.col.findAndModify({
query: {
items: {
$elemMatch: {
prodType: "hardware"
}
}
},
update: {
$set: {
"items.$.checked": true
}
}
})
Okay, but this isn't the whole story. findAndModify will return the whole matched document, and I want to project specifically the array item that was matched (and also modified), so I'll add a fields section to my query:
db.col.findAndModify({
query: {
items: {
$elemMatch: {
prodType: "hardware"
}
}
},
update: {
$set: {
"items.$.checked": true
}
},
fields: {
items: {
$elemMatch: {
prodType: "hardware"
}
}
}
})
And now to the question: does MongoDB guarantee that the returned array item from my query is the exact same one that was matched (and modified) in the update section even though we have two items matching the criteria?
YES. It will return only the first sub-document that matched your criteria and was modified in the update section as shown here
According to the official docs, then yes - the projected array element is the exact one that was modified using the same one modified by the positional operator.
$ (update) states:
the positional $ operator acts as a placeholder for the first element that matches the query document
and $elemMatch (projection) states:
The $elemMatch operator limits the contents of an <array> field from the query results to contain only the first element matching the $elemMatch condition
They both apply to the first array element so it directly implies that the modified array element is the one that is projected

MongoDB update objects nested in arrays nested in other arrays without using the array index (nested updating)

I have the following document:
{
"_id" : ObjectId("5720bd01232ac527623f2889"),
"planes" : [
{
"name" : "plane1",
"wings" : [
{
"name" : "rightDown"
},
{
"name" : "rightUp"
}
]
},
{
"name" : "plane2",
"wings" : [
{
"name" : "leftUp",
},
{
"name" : "leftDown",
}
]
}
]
}
I want to update an object in an array nested in an another array, without using the array index.
In my example, 'plane2' with the property 'wing' named 'leftDown'. Is it possible ?
db.planes.update({
planes: {
$elemMatch: {
wings: {
$elemMatch: {
name: 'leftUp'
}
}
}
}
},
// It would be wonderful if I the $set would look like this,
// but the error is:
// "Too many positional (i.e. '$') elements found in path 'planes.$.wings.$'"
//
// It seems that $ holds only the value of the first nested
// object in the array
{
$set: {
'planes.$.wings.$': {
name: 'leftMagic'
}
}
})
MongoDb 3.2 documentation says:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
But I'm still waiting for that miracle ..
Are there any other clean/beautiful ways to do the update in a single shoot ?
Sadly no, the positional operator can only hold a single value. I don't think you can perform this update in a single shot with your current data structure.
You could perform one lookup to get the first array index, then a second with the positional operator, but at that point you could just traverse the array and resave the document.
With a little restructuring of your collections you could get the update down to a single operation. Check out MongooseJS populate.