Sort query for dynamic array in Mongo DB - mongodb

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

Related

MongoDB query for documents with nested objects and array

I am having trouble with mongodb querying my object document.
I have the following document:
{
"_id" : ObjectId("1"),
"name" : "Bob",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"2022-08-01"
]
}
]
},
{
"_id" : ObjectId("2"),
"name" : "John",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"1901-08-01"
]
}
]
}
I am trying to get the document where fields.date[0].values is equal to 2022-08-01.
How can I achieve that? Thank you.
You are looking for a nested element value which having some specific value/date.
Since your document is incomplete so, I am creating one for illustrating the solution.
db.employees.insertMany([{
"name" : "Bob",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"2022-08-01"
]
}
]
}},
{
"name" : "John",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"1901-08-01"
]
}
]
}
},
{
"name" : "Eve",
"fields" : {
"DATE" : [
{
"fromDate" : null,
"values" : [
"2022-08-01"
]
}
]
}},
]);
Now we can find the specific value (2022-08-01 as per your requirement) from the nested array/element inside the document. Since the value resides inside "DATE" which is inside the "fields". So we can get that value easily by calling "fields.DATE".
For example you can write some code like this :
db.employees.find({"fields.DATE":{"$elemMatch": {"values": "2022-08-01"}}})
From the above code you will get a result like this.
{ "_id" : ObjectId("62e94392389644cb3fc9c81f"), "name" : "Bob", "fields" : { "DATE" : [ { "fromDate" : null, "values" : [ "2022-08-01" ] } ] } }
{ "_id" : ObjectId("62e94392389644cb3fc9c821"), "name" : "Eve", "fields" : { "DATE" : [ { "fromDate" : null, "values" : [ "2022-08-01" ] } ] } }
Hope this will help you to solve this issue. Happy coding:)

$elemMatch and $in. query for array of documents

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"
}
}

How to check if nested arrays are ALL empty in mongodb?

I have something like below:
{
"_id" : "1",
"firstArray" : [
{
"_id" : "11",
"secondArray" : [ ]
},
{
"_id" : "12",
"secondArray" : [ ]
},
{
"_id" : "13",
"secondArray" : [ { "type" : "somthing" } ]
}
]
},
{
"_id" : "2",
"firstArray" : [
{
"_id" : "21",
"secondArray" : [ ]
},
{
"_id" : "22",
"secondArray" : [ ]
}
]
}
I need a mongodb query to find documents which ALL of the nested secondArrays are empty? the query should return second document and not the first one.
to solve that, we need to check size of arr2, but to enable that we need first to unwind arr1.
Please find below aggregation framework snippet which solves this problem,
db.pmoubed.aggregate([{
$unwind : "$firstArray"
}, {
$project : {
_id : 1,
firstArray : 1,
isNotEmpty : {
$size : "$firstArray.secondArray"
}
}
}, {
$group : {
_id : "$_id",
isNotEmpty : {
$sum : "$isNotEmpty"
},
firstArray : {
$push : "$firstArray"
}
}
}, {
$match : {
"isNotEmpty" : 0
}
}
])
Any comments welcome

Return intersection of subdocument array with user defined array?

I am trying to use Mongo Aggregation Framework to find out intersection between an array inside my document AND another user defined array.
I don't get a correct result and my guess is its because of the fact that I have array inside of an array.
Here is my data set.
My documents:
{
"_id" : 1,
"pendingEntries" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65f"),
"tags" : [
{
"tagKey" : "owner",
"tagValue" : "john"
},
{
"tagKey" : "ErrorCode",
"tagValue" : "7001"
},
{
"tagKey" : "ErrorDescription",
"tagValue" : "error123"
}
],
"entryTime" : ISODate("2016-04-04T00:26:43.167Z")
}
]
},
/* 1 */
{
"_id" : 2,
"pendingEntries" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65d"),
"tags" : [
{
"tagKey" : "owner",
"tagValue" : "peter"
},
{
"tagKey" : "ErrorCode",
"tagValue" : "6001"
},
{
"tagKey" : "JIRA",
"tagValue" : "Oabc-123"
}
],
"entryTime" : ISODate("2016-04-04T00:26:43.167Z")
}
]
},
/* 2 */
{
"_id" : 3,
"pendingEntries" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65c"),
"tags" : [
{
"tagKey" : "owner",
"tagValue" : "abc"
},
{
"tagKey" : "ErrorCode",
"tagValue" : "6001"
},
{
"tagKey" : "JIRA",
"tagValue" : "abc-123"
}
],
"entryTime" : ISODate("2016-04-04T00:26:43.167Z")
}
]
}
My Query:
db.entrylike.aggregate(
[
{ $project: { "pendingEntries.entryID": 1, "common": { $setIntersection: [ "$pendingEntries.tags", [{ "tagKey" : "ErrorCode", "tagValue" : "7001" }] ] } } }
]
)
Result:
{
"result" : [
{
"_id" : 1,
"pendingEntries" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65f")
}
],
"common" : []
},
{
"_id" : 2,
"pendingEntries" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65d")
}
],
"common" : []
},
{
"_id" : 3,
"pendingEntries" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65c")
}
],
"common" : []
}
],
"ok" : 1
}
I am not expecting first common field to be empty. Can someone let me know what is it that I am doing wrong? Or any work arounds that I can take.
I am using mongodb 3.0.8. I am aware of the fact that Mongodb 3.2 can offer some features which will fulfill my needs but 3.2 upgrade is not in our pipeline soon and I am looking to resolve this using Mongo3.0 if possible.
My goal is to either replace tags array with the common elements from the user defined list or add a new field with common elements. My am trying to to the later in my example.
The reason you the common field is empty is because your "pendingEntries" array and your user defined array have not element in common. What you really want is to return an array that contains the elements that appear in your "tags" array and your user defined array. To do that you can simply use the $map operator and apply the $setIntersection operator to each subdocument "tags" in the "pendingEntries" array.
db.entrylike.aggregate([
{ "$project": {
"common": {
"$map": {
"input": "$pendingEntries",
"as": "p",
"in": {
"entryID": "$$p.entryID",
"tags": {
"$setIntersection": [
"$$p.tags",
{ "$literal": [
{
"tagKey" : "ErrorCode",
"tagValue" : "7001"
}
]}
]
}
}
}
}
}}
])
Which returns:
{
"_id" : 1,
"common" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65f"),
"tags" : [
{
"tagKey" : "ErrorCode",
"tagValue" : "7001"
}
]
}
]
}
{
"_id" : 2,
"common" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65d"),
"tags" : [ ]
}
]
}
{
"_id" : 3,
"common" : [
{
"entryID" : ObjectId("5701b4c3c6b126083332e65c"),
"tags" : [ ]
}
]
}

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.