$elemMatch and $in. query for array of documents - mongodb

having a collection(productList) and document like below:
{ "_id":"1",
"product_name" : "Haier 240l",
"version" : "1.0",
"filterInfo" : [
{
"name" : "brand",
"value" : "Haier"
},
{
"name" : "energy_rating",
"value" : "4 Star"
}
]
}
{ "_id":"2",
"product_name" : "Haier 310l",
"version" : "1.0",
"filterInfo" : [
{
"name" : "brand",
"value" : "Haier"
},
{
"name" : "energy_rating",
"value" : "3 Star"
}
]
}
{ "_id":"3",
"product_name" : "Samsung 275l",
"version" : "1.0",
"filterInfo" : [
{
"name" : "brand",
"value" : "Samsung"
},
{
"name" : "energy_rating",
"value" : "3 Star"
}
]
}
{ "_id":"4",
"product_name" : "Lg 254l",
"version" : "1.0",
"filterInfo" : [
{
"name" : "brand",
"value" : "Lg"
},
{
"name" : "energy_rating",
"value" : "3 Star"
}
]
}
I want to fetch all document with (i), brand:Haier and energy_rating:3
And (ii) brand:Haier or brand:Samsung and energy_rating:3
For (i) I tried, using elemMatch below queries:
db.productList.find({filterInfo: { $elemMatch: {'name':{$in:['brand','enery_rating']},'value':{$in:['Samsung','3 Star']}}}})
db.getCollection('productList').find({'filterInfo': {$elemMatch: { 'value':'Samsung', 'value' :'3 Star'} } })
db.getCollection('productList').find({'filterInfo': {$elemMatch: { $and:[ {'name':'brand', 'value' :'Samsung'}, {'name':'energy_rating', 'value':'3 Star' } ]} } })
db.getCollection('productList').find({'filterInfo': {$elemMatch: { 'name':'brand', 'value' :'Samsung', 'name':'energy_rating', 'value':'3 Star'} } })
But none of these is working correctly. I am doing some bascis wrong here (Novice here).

First Query
db.productList.find({
"$and":[
{"filterInfo":{"$elemMatch":{"name":"brand","value":"Haier"}}},
{"filterInfo":{"$elemMatch":{"name":"energy_rating","value":"3 Star"}}}
]
})
You can simplify the first query using $all with $elemMatch to perform queries on arrays.
db.productList.find({
"filterInfo":{
"$all":[
{"$elemMatch":{"name":"brand","value":"Haier"}},
{"$elemMatch":{"name":"energy_rating","value":"3 Star"}}
]
}
})
Second Query
db.productList.find({
"$and":[
{"$or":[
{"filterInfo":{"$elemMatch":{"name":"brand","value":"Samsung"}}},
{"filterInfo":{"$elemMatch":{"name":"brand","value":"Haier"}}}
]},
{"filterInfo":{"$elemMatch":{"name":"energy_rating","value":"3 Star"}}}
]
})

No need for $elemMatch in this, I would say.
First Query
db.productList.find({
$and:[
{'filterInfo.value':'Haier'},
{'filterInfo.value':'3 Star'}
]
});
Second Query
db.productList.find({
$and:[
{'filterInfo.value':{
$in:['Haier','Samsung']}
},
{'filterInfo.value':'3 Star'}
]
});
Concentrate more on data structuring in MongoDB for optimized performance.
I don't know your application logic, But is it necessary to keep all those data in an array. I don't see the need of that when you can keep data simple like JSON 1 or if you are specific about filterInfo key can be designed like JSON 2.
JSON 1
{
"_id" : "1",
"product_name" : "Haier 240l",
"version" : "1.0",
"brand":"Haier",
"energy_rating":"4 Star"
}
JSON 2
{
"_id" : "1",
"product_name" : "Haier 240l",
"version" : "1.0",
"filterInfo": {
"brand":"Haier",
"energy_rating":"4 Star"
}
}

Related

Sort query for dynamic array in Mongo DB

I have a collection in MongoDB like below format. Now i want to know how to apply sorting. Please find example of collection below,
{
"_id" : ObjectId("5e5e140f113a6c3970eef3bb"),
"FormId" : "5cd3a0a0cb20953208fcb549",
"FieldsDatas" : [
{
"FieldId" : "4fbcef5b-d60a-4908-a037-5ff085b70709",
"Value" : [
"202003031"
]
},
{
"FieldId" : "708e1baf-fcd0-45fa-b1de-27f34391c35c",
"Value" : [
"202003031"
]
},
{
"FieldId" : "0b563a80-2b0a-4803-ad7f-652a381e134c",
"Value" : [
"New Source",
"Endpoint"
]
},
{
"FieldId" : "15355b82-4fae-4c09-acb4-13f95e8c2d4e",
"Value" : [
"2020-03-03 13:51:17"
]
},
{
"FieldId" : "1e32a283-34a9-4a7b-b851-3e5ac7f93d2c",
"Value" : [
"tets"
]
}
]
}
{
"_id" : ObjectId("5e5e1c89113a6c3970eef3bc"),
"FormId" : "5cd3a0a0cb20953208fcb549"
"FieldsDatas" : [
{
"FieldId" : "708e1baf-fcd0-45fa-b1de-27f34391c35c",
"Value" : [
"202003032"
]
},
{
"FieldId" : "0eca0881-a69b-4db3-b8b1-d74b0a16d4ef",
"Value" : [
"20200303_2#mail.com"
]
},
{
"FieldId" : "2da67714-aaf3-433d-9a86-1b48c75470ec",
"Value" : [
"mani lj"
]
},
{
"FieldId" : "a0b26aac-cad0-4c5e-83b4-9a01ac4ce97a",
"Value" : []
},
{
"FieldId" : "15355b82-4fae-4c09-acb4-13f95e8c2d4e",
"Value" : [
"2020-03-03 14:29:23"
]
}
]
}
For above collection i have date inside the array of objects called FieldsDatas where "FieldId": "15355b82-4fae-4c09-acb4-13f95e8c2d4e" in that same "Value" array has datetime. I want to sort the result based in this datetime.
I have a filter query. How to add sort query for above scenario?
db.getCollection('Collection').find({
"$and":[
{
FieldsDatas: {
$elemMatch:{
FieldId:'955c9843-1535-4df8-a1c4-09430ac9f6ba',
Value: { $ne: ["Contact"] }
}
}
}
]
})
.limit(5)
.skip(30);
Anyone please help me to add sorting for this query like above datetime field.
Any possibilities to achieve this by Stored procedure or functions. Please let me know.
Thanks in advance,
Mani

All Mongo documents with duplicated objects inside array

Find documents with duplicated objects inside an array.
Some answers works just with array made of "basic type elements" (i.e. array of strings). Here I want to filter on certain objects fields
In example:
{
"name": "1",
"arr": [{ "type": "fruit", "name":"pear"},{ "type": "fruit","name":"banana"}]
},
{
"name":"2",
"arr": [{"type":"fish"}]
}
Given the above two documents, I want to retrieve just document 1, because it has 2 elements in the array that have the same type. (Of course I want all documents with such property, not just one)
The following query can get us the expected output:
db.collection.find({
$expr:{
$ne:[
{
$size:"$arr"
},
{
$size:{
$setUnion:["$arr.type"]
}
}
]
}
}).pretty()
Data set:
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133c"),
"name" : "1",
"arr" : [
{
"type" : "fruit",
"name" : "pear"
},
{
"type" : "fruit",
"name" : "banana"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133d"),
"name" : "2",
"arr" : [
{
"type" : "fish"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133e"),
"name" : "3",
"arr" : [
{
"type" : "product",
"name" : "watch"
},
{
"type" : "product",
"name" : "Pen"
}
]
}
Output:
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133c"),
"name" : "1",
"arr" : [
{
"type" : "fruit",
"name" : "pear"
},
{
"type" : "fruit",
"name" : "banana"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133e"),
"name" : "3",
"arr" : [
{
"type" : "product",
"name" : "watch"
},
{
"type" : "product",
"name" : "Pen"
}
]
}
Query analysis: We are filtering documents in which the size of arr is not equal to the count of unique type present in the arr

Update double nested array mongodb

I have the below document which contains double nested array format. I have to update the "level" field to "Senior Engineer" when the "someKey":"somevalue" and "Company":"Company1" and "Name":"Nandhi".
Document
{
"_id" : "777",
"someKey" : "someValue",
"someArray" : [
{
"Company" : "Company1",
"someNestedArray" : [
{
"name" : "Nandhi",
"level" : "Junior Engineer"
},
{
"name" : "Rajan",
"level" : "Senio Engineer"
}
]
}],
{
"Company" : "Company2",
"someNestedArray" : [
{
"name" : "Nandhi",
"level" : "Junior Engineer"
},
{
"name" : "Rajan",
"level" : "Senio Engineer"
}
]
}
]
}
Update Query I tried
db.Test123.updateOne(
{"someKey" : "someValue","someArray.Company":"Company1"},
{$set:{"someArray.$[someNestedArray].level":"Senior Developer"}},
{arrayFilters:[{"someNestedArray.name":"Nandhi"}]}
);
Output Screenshot
As you can seen that, the modifiedCount returns 0. Please advice on this!
You need to define arrayFilter for every level of nesting, try:
db.Test123.update(
{ "someKey" : "someValue" },
{ "$set": { "someArray.$[someArrayDoc].someNestedArray.$[someNestedArrayDoc].level": "Senior Developer" } },
{ arrayFilters: [ {"someArrayDoc.Company": "Company1"}, { "someNestedArrayDoc.name": "Nandhi" } ] }
)

MongoDB Aggregation Projection

If I have a collection as follows:
db.cafe.insert({name: "Cafe1", customers: [{name: "David", foods: [{name : "cheese"}, {name: "beef"}]}, {name: "Bill", foods: [{name: "fish"}]} ]})
db.cafe.find().pretty()
{
"_id" : ObjectId("54f5ae58baed23b7a34fccb6"),
"name" : "Cafe1",
"customers" : [
{
"name" : "David",
"foods" : [
{
"name" : "cheese"
},
{
"name" : "beef"
}
]
},
{
"name" : "Bill",
"foods" : [
{
"name" : "fish"
}
]
}
]
}
How can I extract an array containing just the food objects for people called "David".
Desired output is just the array of foods, i.e:
[{name: "cheese"}, {name: "beef"}]
I have tried an aggregation pipeline that unwinds the cafes customers, then matches on name then projects the food, e.g:
db.cafe.aggregate( [{$unwind : "$customers"}, {$match : {"customers.name": "David"}}, {$project : {"customers.foods": 1, _id : 0}
}] ).pretty()
{
"customers" : {
"foods" : [
{
"name" : "cheese"
},
{
"name" : "beef"
}
]
}
}
This seems close to the desired result, however, I'm left with the issue that the foods I want are referenced as an array under the property customers.foods. I would like the result to directly be:
[
{
"name" : "cheese"
},
{
"name" : "beef"
}
]
is there a way I can achieve the desired output?
You are doing your projection wrong.
db.cafe.aggregate( [
{ "$match" : { "customers.name": "David" }},
{ "$unwind" : "$customers" },
{ "$project" : { "foods": "$customers.foods", "_id": 0 }}
])
Output
{ "foods" : [ { "name" : "cheese" }, { "name" : "beef" } ] }
You can also get (something very, very close to) your desired output with a regular query:
> db.cafe.find({ "customers.name" : "David" }, { "customers.$.foods" : 1, "_id" : 0 })
{ "customers" : [ { "name" : "David", "foods" : [ { "name" : "cheese" }, { "name" : "beef" } ] } ] }
Customers will be an array containing just the first object with name : "David". You should prefer this approach to the aggregation as it's vastly more performant. You can extract the foods array in client code.

Aggregate of different subtypes in document of a collection

abstract document in collection md given:
{
vals : [{
uid : string,
val : string|array
}]
}
the following, partially correct aggregation is given:
db.md.aggregate(
{ $unwind : "$vals" },
{ $match : { "vals.uid" : { $in : ["x", "y"] } } },
{
$group : {
_id : { uid : "$vals.uid" },
vals : { $addToSet : "$vals.val" }
}
}
);
that may lead to the following result:
"result" : [
{
"_id" : {
"uid" : "x"
},
"vals" : [
[
"24ad52bc-c414-4349-8f3a-24fd5520428e",
"e29dec2f-57d2-43dc-818a-1a6a9ec1cc64"
],
[
"5879b7a4-b564-433e-9a3e-49998dd60b67",
"24ad52bc-c414-4349-8f3a-24fd5520428e"
]
]
},
{
"_id" : {
"uid" : "y"
},
"vals" : [
"0da5fcaa-8d7e-428b-8a84-77c375acea2b",
"1721cc92-c4ee-4a19-9b2f-8247aa53cfe1",
"5ac71a9e-70bd-49d7-a596-d317b17e4491"
]
}
]
as x is the result aggregated on documents containing an array rather than a string, the vals in the result is an array of arrays. what i look for in this case is to have a flattened array (like the result for y).
for me it seems like that what i want to achieve by one aggegration call only, is currently not supported by any given operation as e.g. a type conversion cannot be done or unwind expectes in every case an array as input type.
is map reduce the only option i have? if not ... any hints?
thanks!
You can use the aggregation to do the computation you want without changing your schema (though you might consider changing your schema simply to make queries and aggregations of this field easier to write).
I broke up the pipeline into multiple steps for readability. I also simplified your document slightly, again for readability.
Sample input:
> db.md.find().pretty()
{
"_id" : ObjectId("512f65c6a31a92aae2a214a3"),
"uid" : "x",
"val" : "string"
}
{
"_id" : ObjectId("512f65c6a31a92aae2a214a4"),
"uid" : "x",
"val" : "string"
}
{
"_id" : ObjectId("512f65c6a31a92aae2a214a5"),
"uid" : "y",
"val" : "string2"
}
{
"_id" : ObjectId("512f65e8a31a92aae2a214a6"),
"uid" : "y",
"val" : [
"string3",
"string4"
]
}
{
"_id" : ObjectId("512f65e8a31a92aae2a214a7"),
"uid" : "z",
"val" : [
"string"
]
}
{
"_id" : ObjectId("512f65e8a31a92aae2a214a8"),
"uid" : "y",
"val" : [
"string1",
"string2"
]
}
Pipeline stages:
> project1 = {
"$project" : {
"uid" : 1,
"val" : 1,
"isArray" : {
"$cond" : [
{
"$eq" : [
"$val.0",
[ ]
]
},
true,
false
]
}
}
}
> project2 = {
"$project" : {
"uid" : 1,
"valA" : {
"$cond" : [
"$isArray",
"$val",
[
null
]
]
},
"valS" : {
"$cond" : [
"$isArray",
null,
"$val"
]
},
"isArray" : 1
}
}
> unwind = { "$unwind" : "$valA" }
> project3 = {
"$project" : {
"_id" : 0,
"uid" : 1,
"val" : {
"$cond" : [
"$isArray",
"$valA",
"$valS"
]
}
}
}
Final aggregation:
> db.md.aggregate(project1, project2, unwind, project3, group)
{
"result" : [
{
"_id" : "z",
"vals" : [
"string"
]
},
{
"_id" : "y",
"vals" : [
"string1",
"string4",
"string3",
"string2"
]
},
{
"_id" : "x",
"vals" : [
"string"
]
}
],
"ok" : 1
}
If you modify your schema using always "vals.val" field as an array field (even when the record contains only one element) you can do it easily as follows:
db.test_col.insert({
vals : [
{
uid : "uuid1",
val : ["value1"]
},
{
uid : "uuid2",
val : ["value2", "value3"]
}]
});
db.test_col.insert(
{
vals : [{
uid : "uuid2",
val : ["value4", "value5"]
}]
});
Using this approach you only need to use two $unwind operations: one unwinds the "parent" array and the second unwinds every "vals.val" value. So, querying like
db.test_col.aggregate(
{ $unwind : "$vals" },
{ $unwind : "$vals.val" },
{
$group : {
_id : { uid : "$vals.uid" },
vals : { $addToSet : "$vals.val" }
}
}
);
You can obtain your expected value:
{
"result" : [
{
"_id" : {
"uid" : "uuid2"
},
"vals" : [
"value5",
"value4",
"value3",
"value2"
]
},
{
"_id" : {
"uid" : "uuid1"
},
"vals" : [
"value1"
]
}
],
"ok" : 1
}
And no, you can't execute this query using your current schema, since $unwind fails when the field isn't an array field.