$elemMatch query in MongoDB - mongodb

I have a collection 'name' with 2 documents of the structure :
doc 1:
{
a: 1
b : [{name:"AAA",age:10},
{name:"BBB",age:12},
{name:"CCC",age:13}]
}
doc 2 :
{
a: 2
b : [{name:"DDD",age:14},
{name:"EEE",age:15},
{name:"FFF",age:16}]
}
Since I am new to MongoDB, I am trying to find the difference of using the $elemMatch operator and not using it. Basically I am trying to query and find the first doc ( doc 1) with name AAA and age 10. So using the $elemMatch, my query looks like this :
db.name.find({b: {$elemMatch :{name:"AAA",age:10}}})
This query works perfectly fine, but my question is that what's the need to use this query when I can query like this :
db.name.find({b:{name:"AAA",age:10}})
I am sure there should be some reason for $elemMatch, just trying to find the difference. Thanks in advance for the reply !!!

The key difference is that the second query (without the $elemMatch) would only match elements in the b array that only contained those two fields, and only in that order.
So the first query would match the following two documents, but the second query wouldn't:
{
a: 1
b: [{name: "AAA", age: 10, city: 'New York'},
{name: "BBB", age: 12, city: 'Paris'},
{name: "CCC", age: 13, city: 'London'}]
}
{
a: 1,
b: [{age: 10, name: "AAA"},
{name: "BBB", age: 12},
{name: "CCC", age: 13}]
}

Another important difference is that how Mongo uses indexes.
If we have declared a multi-key-compound index:
db.name.createIndex({ "b.name": 1, "b.age": 1 })
And we execute this:
db.name.explain().find({
b: {
name: "DDD",
age: 14
}
})
we get:
"winningPlan" : {
"stage" : "COLLSCAN",
If we execute this:
db.name.explain().find({
b: {
$elemMatch: {
name: "DDD",
age: 14
}
}
})
we get:
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
But if we have a simple multi-key index in the array:
db.name.createIndex({b: 1})
The above index will be used in this query:
db.name.explain().find({
b: {
name: "DDD",
age: 14
}
})
And under my very un-professional profiling tests looks like this is faster.

Related

Mongoose retun nModified : 1 when no data is updated

Trying to understand why/if mongoose is updating my documents even though no data is changed?
If I save a new document with the query below it will return this in the console.log(item)
{ n: 1,
nModified: 0,
upserted: [ { index: 0, _id: 5f3d35c386aeb6c6fb35fa79 } ],
ok: 1 }
Query
Product.updateOne(
{productName: product.productName},
{$set: newProduct},
{upsert: true}
).then((item) => {
console.log(item);
}).catch((e) => {
console.log('Insert error', e);
});
If i rerun the same query again i get this back. This indicates that the document has been modified but the data is the same, there is no new data that has been inserted.
{ n: 1, nModified: 1, ok: 1 }
I've noticed if i remove the stores array, delete the document, insert it again and rerun the query I get { n: 1, nModified: 0, ok: 1 } back in the console.log(item)
I run the same querys, the same amout of time, but when having an array in the object i get this { n: 1, nModified: 1, ok: 1 } and when not having an array a get this { n: 1, nModified: 0, ok: 1 }
It seems that when having an array the document gets modified regardless if the data is changed.
Example 1
Gives { n: 1, nModified: 1, ok: 1 }
const newProduct = {
ean: product.ean,
productName: product.productName,
lowestPrice: product.productPrice,
mainCategory: categories.mainCategory,
group: categories.group,
subCategory: categories.subCategory,
subSubCategory: subSubCat,
stores: [{
name: "foobar",
}],
};
Example 2
Gives { n: 1, nModified: 0, ok: 1 }
const newProduct = {
ean: product.ean,
productName: product.productName,
lowestPrice: product.productPrice,
mainCategory: categories.mainCategory,
group: categories.group,
subCategory: categories.subCategory,
subSubCategory: subSubCat,
};
Is it me who misunderstands the operation below or whats going on?
What i want to do is:
1.insert if the document don't exists based on productName,
2.if something differs in the document stored in the database and the newProduct, update the document.
3.If nothing differs, do nothing
Product model
const ProductSchema = new Schema({
ean: String,
productName: String,
mainCategory: String,
subCategory: String,
group: String,
subSubCategory: String,
lowestPrice: Number,
isPopular: Boolean,
description: String,
stores: [
{
name: String,
},
],
});
Edit: As its pretty hard to explain I created a small repo that shows the issue.
https://github.com/gameatrix/mongo_array
The database still performs the update.
For example, let's conditionally upsert a value:
MongoDB Enterprise ruby-driver-rs:PRIMARY> db.foo.update({a:42},{a:42},{upsert:true})
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("5f3d4ed509fcd40c9f092690")
})
MongoDB Enterprise ruby-driver-rs:PRIMARY> db.foo.update({a:42},{a:42},{upsert:true})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
The first write was an insert, the second write was an update. The second write did not change any data but the database performed a write.
You can verify there was a write by using a change stream in another shell instance:
MongoDB Enterprise ruby-driver-rs:PRIMARY> db.foo.watch()
{ "_id" : { "_data" : "825F3D4ED5000000012B022C0100296E5A1004FAC29486D5A3459A8726349007F2E43E46645F696400645F3D4ED509FCD40C9F0926900004" }, "operationType" : "insert", "clusterTime" : Timestamp(1597853397, 1), "fullDocument" : { "_id" : ObjectId("5f3d4ed509fcd40c9f092690"), "a" : 42 }, "ns" : { "db" : "test", "coll" : "foo" }, "documentKey" : { "_id" : ObjectId("5f3d4ed509fcd40c9f092690") } }
{ "_id" : { "_data" : "825F3D4ED6000000012B022C0100296E5A1004FAC29486D5A3459A8726349007F2E43E46645F696400645F3D4ED509FCD40C9F0926900004" }, "operationType" : "replace", "clusterTime" : Timestamp(1597853398, 1), "fullDocument" : { "_id" : ObjectId("5f3d4ed509fcd40c9f092690"), "a" : 42 }, "ns" : { "db" : "test", "coll" : "foo" }, "documentKey" : { "_id" : ObjectId("5f3d4ed509fcd40c9f092690") } }
By definition an upsert either modifies documents that match the condition or inserts new documents. You are always going to have a write when upserting.
2.if something differs in the document stored in the database and the newProduct, update the document.
The bolded part is not how MongoDB (and most databases, as far as I know) work. Whether a write is performed does not depend on whether the data being written is the same as what is already in the database.

Mongodb- using find() method on an Array of Objects only return first match instead of all

Unlike the other question someone asked where they wanted only one item returned. I HAVE one item returned and I need ALL of the matching objects in the array return. However the second object that matches my query is being completely ignored.
This is what one of the items in the item collection looks like:
{
name: "soda",
cost: .50,
inventory: [
{ flavor: "Grape",
amount: 8 },
{ flavor: "Orange",
amount: 4 },
{ flavor: "Root Beer",
amount: 15 }
]
}
Here is the query I typed in to mongo shell:
Items.find({"inventory.amount" : { $lte : 10} } , { name : 1, "inventory.$.flavor" : 1})
And here is the result:
"_id" : ObjectId("59dbe33094b70e0b5851724c"),
"name": "soda"
"inventory" : [
{ "flavor" : "Grape",
"amount" : 8,
}
]
And here is what I want it to return to me:
"_id" : ObjectId("59dbe33094b70e0b5851724c"),
"name": "soda"
"inventory" : [
{ "flavor" : "Grape",
"amount" : 8
},
{ "flavor" : "Orange",
"amount" : 4
}
]
I'm new to mongo and am dabbling to get familiar with it. I've read through the docs but couldn't find a solution to this though it's quite possible I overlooked it. I'd really love some help. Thanks in advance.
first u can get your result by this query
db.Items.find({"inventory.amount" : { $lte : 10} } , { name : 1, "inventory.flavor" : 1 , "inventory.amount" : 1})

mongo query for multiple condition

I have data,
var data = [{name: 'n1', age: 22}, {name: 'n1', age: age: 11}, {name: 'n2', age: age: 16}, {name: 'n3', age: 22}];
and I want to get data according to my multiple conditions:
condition 1:- var query = {name: 'n1', age: 22};
condition 2:- var query = {name = '', age: 22}
What I want with a single query:
If I run condition 1 then the result should be
[{name: 'n1', age: 22}].
and if I run condition 2, then the result should be
[{name: 'n1', age: 22}, {name: 'n3', age: 22} ].
Means for second condition, query should search only with age field.
That all I want to implement in one query.
I am doing like:
$query:{$and: [{name: query.name}, {age: query.age}]}
It works for first condition but not for second.
How can achieve it in one query?
Code it up so that only the fields you're interested in are in the query:
let criteria = [];
if (query.name && query.name.length > 0) {
criteria.push({ name: query.name });
}
if (query.age && query.age > 0) {
criteria.push({ age: query.age });
}
criteria = criteria.length > 0 ? { $and: criteria } : {};
Now you can pass your criteria to the query.
You could create a query object that can be initialised based on the two conditions above. The following snippet demonstrates this:
Populate test collection
db.test.insert([
{name: 'n1', age: 22},
{name: 'n1', age: 11},
{name: 'n2', age: 16},
{name: 'n3', age: 22}
])
Initialise conditions
var criteria = {},
condition = [
{"name": query.name},
{"age": query.age}
];
if (query.name !== "") { criteria["$and"] = condition; }
else { criteria["$or"] = condition; }
Test criteria for condition 1)
var query = {"name": "n1", "age": 22 }
printjson(criteria);
db.test.find(criteria);
Sample Output
{ "$and" : [ { "name" : "n1" }, { "age" : 22 } ] }
{ "_id" : ObjectId("56b341802ae7a05a8444cedc"), "name" : "n3", "age" : 22 }
Test criteria for condition 2)
var query = {"name": "", "age": 22 }
printjson(criteria);
db.test.find(criteria);
Sample Output
{ "$or" : [ { "name" : "" }, { "age" : 22 } ] }
{ "_id" : ObjectId("56b341802ae7a05a8444ced9"), "name" : "n1", "age" : 22 }
{ "_id" : ObjectId("56b341802ae7a05a8444cedc"), "name" : "n3", "age" : 22 }

Mongo update based on array index

I have a list of people in my database, where each person is one document. I would like to give them standard names (this is just for demo purposes – I don't care who gets which name so long as its unique).
I have an array of names to give them but I am not sure how to do this in Mongo. One simple way would be to just have a cursor and update the documents one by one, but that seems inefficient. Is there a better way to do this?
EDIT:
example db:
{id: 1234, age: 50}
{id: 1235, age: 40}
{id: 1236, age: 30}
Names:
['Bob', 'Jill', 'Gina']
Desired end state:
{id: 1234, age: 50, name: 'Bob'}
{id: 1235, age: 40, name: 'Jill'}
{id: 1236, age: 30, name: 'Gina'}
I'm not sure how you are specifying which names go with which _ids, so I'm going to assume you have a mapping specified in the following form:
> nameMapping
[{ "_id" : 1234, "name" : "Bob" }, { "_id" : 1235, "name" : "Jill" }, { "_id" : 1236, "name" : "Gina" }]
Then a good way to update every doc with a name is to use bulk operations. Bulk operations are supported in the drivers but I'll give the example in the mongo shell with bulk.find.update(), which is available as of MongoDB 2.6.
> var bulk = db.people.initializeUnorderedBulkOp()
> nameMapping.forEach(function(pair) {
bulk.find( { "_id" : pair._id } ).update( { $set: { "name" : pair.name } } )
})
> bulk.execute()

Mongodb: find embedded element missing some key

I have a document with an embedded collection, but few elements are missing a key and I have to find all those elements. Here is an example:
var foo = {name: 'foo', embedded: [{myKey: "1", value: 3}, {myKey: "2", value: 3}]}
db.example.insert(foo)
var bar = {name: 'bar', embedded: [{value: 4}, {myKey: "3", value: 1}]}
db.example.insert(bar)
I need a query that returns the 'bar' object because one of its embedded doesn't have the key 'myKey'.
I try to use the $exists, but it returns only if ALL embedded elements are missing the key
db.example.find({'embedded.myKey': {$exists: true}}).size()
// -> 2
db.example.find({'embedded.myKey': {$exists: false}}).size()
// -> 0
How can I find the documents that at least one embedded element is missing the key 'myKey'?
If 'value' is always present, then you can try this command
db.example.find({ embedded : { $elemMatch : { value : {$exists : true}, myKey : {$exists : false}} }})
{ "_id" : ObjectId("518bbccbc9e49428608691b0"), "name" : "bar", "embedded" : [ { "value" : 4 }, { "myKey" : "3", "value" : 1 } ] }