MongoDB merge Array Elements inside an Array - arrays

I have a collection.
{
"_id" : "410a7cb2-7ee1-4e7a-9fb7-fa651fcaa4e5",
"reqHistoryEvents" : [
{
"reqHistoryMsg" : "abcd",
"reqHistoryCreatedAt" : ISODate("2022-04-27T08:18:30.850+0000"),
},
{
"reqHistoryMsg" : "EFGH ",
"reqHistoryCreatedAt" : ISODate("2022-04-27T08:22:12.716+0000"),
},
{
"reqHistoryMsg" : "IJKL",
"reqHistoryCreatedAt" : ISODate("2022-04-27T08:22:12.716+0000"),
}
]
}
I want to convert it to this :::::
{
"_id" : "410a7cb2-7ee1-4e7a-9fb7-fa651fcaa4e5",
"reqHistoryEvents" : [
{
"reqHistoryMsg" : "abcd",
"reqHistoryCreatedAt" : ISODate("2022-04-27T08:18:30.850+0000"),
},
{
"reqHistoryMsg" : ["EFGH ","IJKL"],
"reqHistoryCreatedAt" : ISODate("2022-04-27T08:22:12.716+0000"),
}
]
}
Basically it will be based on the creation Timestamp. We need to merge the reqHistoryMsg if we have same reqHistoryCreatedAt.
I am not able to write the mongo query. Any help?

$unwind - Deconstruct reqHistoryEvents array field.
$group - Group by _id and reqHistoryEvents.reqHistoryCreatedAt fields. Add reqHistoryMsg into an array.
$group - Group by _id field and form reqHistoryEvents array.
db.collection.aggregate([
{
$unwind: "$reqHistoryEvents"
},
{
$group: {
_id: {
_id: "$_id",
reqHistoryCreatedAt: "$reqHistoryEvents.reqHistoryCreatedAt"
},
reqHistoryMsg: {
$push: "$reqHistoryEvents.reqHistoryMsg"
}
}
},
{
$group: {
_id: "$_id._id",
reqHistoryEvents: {
$push: {
reqHistoryMsg: "$reqHistoryMsg",
reqHistoryCreatedAt: "$_id.reqHistoryCreatedAt"
}
}
}
}
])
Sample Mongo Playground

Related

Write a mongo query to count the data where similar data in array?

Sample data: there are multiple similar collection:
{
"_id" : NumberLong(301),
"telecom" : [
{
"countryCode" : {
"value" : "+1"
},
"extension" : [
{
"url" : "primary",
"value" : [
"true"
]
}
],
"modifiedValue" : {
"value" : "8887778888"
},
"system" : {
"value" : "phone"
},
"useCode" : {
"value" : "Home Phone"
},
"value" : {
"value" : "8887778888"
}
},
{
"extension" : [
{
"url" : "primary",
"value" : [
"true"
]
}
],
"modifiedValue" : {
"value" : "abc#test.com"
},
"system" : {
"value" : "email"
},
"useCode" : {
"value" : "work"
},
"value" : {
"value" : "abc#test.com"
}
}
]
}
Issue: I want to cont the collection where telecom.system.value = email and countryCode doesn't exist in the email part object. here I am attaching a script but I need one line query
var count = 0,i;
db.getCollection('practitioner').find({"telecom.system.value":"email"}).forEach(function(practitioner){
//print("updating : " +practitioner._id.valueOf())
telecom = practitioner.telecom.valueOf()
for(i= 0;i<telecom.length;i++){
if(telecom[i].system.value === 'email' && telecom[i].countryCode){
count+=1;
}
}
});
print(" Total count of the practitioner with country code in email object: "+count)
Above mention, the script is working fine and the output is as I expected. but the script is not optimised and I want to write in a single line query. Thanks in advance.
You can try aggregation method aggregate(),
Approach 1:
$match condition for countryCode should exists and system.value should be email
$filter to iterate loop of telecom array and check both condition, this will return expected elements
$size to get total element from above filter result
$group by null and count total
var result = await db.getCollection('practitioner').aggregate([
{
$match: {
telecom: {
$elemMatch: {
countryCode: { $exists: true },
"system.value": "email"
}
}
}
},
{
$project: {
count: {
$size: {
$filter: {
input: "$telecom",
cond: {
$and: [
{ $ne: [{ $type: "$$this.countryCode" }, "missing"] },
{ $eq: ["$$this.system.value", "email"] }
]
}
}
}
}
}
},
{
$group: {
_id: null,
count: { $sum: "$count" }
}
}
]);
print("Total count of the practitioner with country code in email object: "+result[0].count);
Playground
Approach 2:
$match condition for countryCode should exists and system.value should be email
$unwind deconstruct telecom array
$match to filter document using above conditions
$count to get total elements count
var result = await db.getCollection('practitioner').aggregate([
{
$match: {
telecom: {
$elemMatch: {
countryCode: { $exists: true },
"system.value": "email"
}
}
}
},
{ $unwind: "$telecom" },
{
$match: {
"telecom.countryCode": { $exists: true },
"telecom.system.value": "email"
}
},
{ $count: "count" }
]);
print("Total count of the practitioner with country code in email object: "+result[0].count);
Playground
I have not tested the performance but you can check and use as per your requirement.

How to query on embedded documents

{
"_id" : ObjectId("5fa919a49bbe481d117506c9"),
"isDeleted" : 0,
"productId" : 31,
"references" : [
{
"_id" : ObjectId("5fa919a49bbe481d117506ca"),
"languageCode" : "en",
"languageId" : 1,
"productId" : ObjectId("5fa919a49bbe481d117506ba")
},
{
"_id" : ObjectId("5fa91cc7d7d52f1e389dee1f"),
"languageCode" : "ar",
"languageId" : 2,
"productId" : ObjectId("5fa91cc7d7d52f1e389dee1e")
}
],
"createdAt" : ISODate("2020-11-09T10:27:48.859Z"),
"updatedAt" : ISODate("2020-11-09T10:27:48.859Z"),
"__v" : 0
},
{
"_id" : ObjectId("5f9aab1d8e475489270ebe3a"),
"isDeleted" : 0,
"productId" : 21,
"references" : [
{
"_id" : ObjectId("5f9aab1d8e475489270ebe3b"),
"languageCode" : "en",
"languageId" : 1,
"productId" : ObjectId("5f9aab1c8e475489270ebe2d")
}
],
"createdAt" : ISODate("2020-10-29T11:44:29.852Z"),
"updatedAt" : ISODate("2020-10-29T11:44:29.852Z"),
"__v" : 0
}
This is my mongoDB collection in which i store the multilingual references to product collection. In productId are the references to product Collection. Now If we have ar in our request, then we will only have the productId of ar languageCode. If that languageCode does not exist then we will have en langCode productId.
For Example if the user pass ar then the query should return
"productId" : ObjectId("5fa91cc7d7d52f1e389dee1e")
"productId" : ObjectId("5f9aab1c8e475489270ebe2d")
I have tried using $or with $elemMatch but I am not able to get the desired result. Also i am thinking of using $cond. can anyone help me construct the query.
We can acheive
$facet helps to categorized the incoming documents
In the arArray, we get all documents which has"references.languageCode": "ar" (This document may or may not have en), then de-structure the references array, then selecting the "references.languageCode": "ar" only using $match. $group helps to get all productIds which belong to "references.languageCode": "ar"
In the enArray, we only get documents which have only "references.languageCode": "en". Others are same like arArray.
$concatArrays helps to concept both arArray,enArray arrays
$unwind helps to de-structure the array.
$replaceRoot helps to make the Object goes to root
Here is the mongo script.
db.collection.aggregate([
{
$facet: {
arAarray: [
{
$match: {
"references.languageCode": "ar"
}
},
{
$unwind: "$references"
},
{
$match: {
"references.languageCode": "ar"
}
},
{
$group: {
_id: "$_id",
productId: {
$addToSet: "$references.productId"
}
}
}
],
enArray: [
{
$match: {
$and: [
{
"references.languageCode": "en"
},
{
"references.languageCode": {
$ne: "ar"
}
}
]
}
},
{
$unwind: "$references"
},
{
$group: {
_id: "$_id",
productId: {
$addToSet: "$references.productId"
}
}
}
]
}
},
{
$project: {
combined: {
"$concatArrays": [
"$arAarray",
"$enArray"
]
}
}
},
{
$unwind: "$combined"
},
{
"$replaceRoot": {
"newRoot": "$combined"
}
}
])
Working Mongo playground
You can test this solution to see if it is useful for you question:
db.collection.aggregate([
{
$addFields: {
foundResults:
{
$cond: {
if: { $in: ["ar", "$references.languageCode"] }, then:
{
$filter: {
input: "$references",
as: "item",
cond: {
$and: [{ $eq: ["$$item.languageCode", 'ar'] },
]
}
}
}
, else:
{
$filter: {
input: "$references",
as: "item",
cond: {
$and: [{ $eq: ["$$item.languageCode", 'en'] },
]
}
}
}
}
}
}
},
{ $unwind: "$foundResults" },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$foundResults"] } } },
{ $project: { _id: 0, "productId": 1 } }
])

Error: "an object representing an expression must have exactly one field" while using expr in find()

My output for the db.dummy.find(query).count() should be 2 but I am getting the following error.
Please note the "mark" field is in an array and has "string value" therefore used $map because using $convert for array throws conversion error.
db.dummy.find({
$expr: {
$project: {
adjustedGrades: {
$map: {
input: "$data.mark",
as: "grade",
in: {
$lte: [
{
$toInt: "$$grade"
},
5
]
}
}
}
},
$match: {
adjustedGrades: {
$eq: true
}
}
}
}).count()
Error:
error: {
"ok" : 0,
"errmsg" : "An object representing an expression must have exactly one field: { $project: { adjustedGrades: { $map: { input: \"$data.mark\", as: \"grade\", in: { $lte: [ { $toInt: \"$$grade\" }, 5.0 ] } } } }, $match: { adjustedGrades: { $eq: true } } }",
"code" : 15983,
"codeName" : "Location15983"}
Collection:
{ "_id" : "1_0", "data" : [ { "Class" : "DUMMY1", "mark" : "5" } ] }
{ "_id" : "2_0", "data" : [ { "Class" : "DUMMY2", "mark" : "3" } ] }
{ "_id" : "3_0", "data" : [ { "Class" : "DUMMY3", "mark" : "9" } ] }
With help from mongoDB community, I was able to get the solution for conversion and comparison of values in an array that can be used inside db.collection.find() rather than aggregate().
>db.testcol.find({
"$expr": {
"$allElementsTrue": {
"$map": {
"input": "$data",
"as": "d",
"in": {
"$gt": [
{
"$toDouble": "$$d.userDL"
},
5
]
}
}
}
}
})
Output: 2
This should help
db.getCollection("dummy").find({ "data.0. mark": { $lte: "5" } }, {})
From MongoDB Documentation,
$expr:
Allows the use of aggregation expressions within the query language.
$expr has the following syntax:
{ $expr: { <expression> } }
The arguments can be any valid aggregation expression. For more information, see Expressions.
$project cannot be used inside $expr
In order to achieve your use case, you can use aggregation framework:
db.dummy.aggregate([
{
$project: {
adjustedGrades: {
$map: {
input: "$data",
as: "grade",
in: {
$lte: [
{
$toInt: "$$grade.mark"
},
5
]
}
}
}
}
},
{
$match: {
adjustedGrades: {
$eq: true
}
}
},
{
"$count": "count"
}
])
MongoDB Playground:Using aggregate()
If you don't want to use aggregation framework, try the following method:
First do the conversion of string to Int using parseInt() :
db.dummy.find().forEach(function(doc) {
doc.data.forEach(function(d) {
d.mark=parseInt(d.mark); })
db.dummy.save(doc);
})
And then run the find() query:
db.dummy.find({
"data.mark": {
$lte: 5
}
}).count()

In Mongo, how can I use $IN condition with values coming from another field?

Data :
{
_id :1111,
col1_array : ['a','b','c'],
col2_array : ['a','f','g']
}
I would like to find all documents where col2_array contains any value of col1_array. I tried using the $IN condition but failed to refer to other field content as an array. How can I do ?
db.collection.aggregate([
{ $unwind: "$col2_array" },
{
$project:
{
index: { $indexOfArray: ["$col1_array", "$col2_array"] },
col2_array: 1,
col1_array: 1,
_id: 1
}
},
{ $match: { index: { $gte: 0 } } },
{ $group: { _id: "$_id" } }
])
After that you have to find records of this particular _id
db.collection.find({_id:{$in:[result_of_above_query_ids]}})
I have finally resolved the problem using arrays intersections I have just discovered digging Mongo documentation :
db.getCollection('test').aggregate([
{$set : {
commonValues: { $setIntersection: [ "$col1_array", "$col2_array"]},
}},
{$match : {"commonValues" : {"$ne" : []}}}
])

MongoDB query subarray in sub array

i want fetch a unitHouse from my document which is an sub array of sub array
Here is the data
{
"_id" : ObjectId("5a17d305c438324308bffb19"),
"floorRow" : [
{
"floorRowNo" : "F1",
"floorRowInfo" : "Best Floor Ever that i have ever seen",
"_id" : ObjectId("5a17d333c438324308bffb1a"),
"unitHouse" : [
]
},
{
"floorRowNo" : "F2",
"floorRowInfo" : "view",
"_id" : ObjectId("5a1bdfbb4d63841c3cb6fc89"),
"unitHouse" : [
{
"unitHouseNo" : "Unit001",
"unitHouseType" : "OFFICE",
"unitHouseStatus" : "SELL",
"_id" : ObjectId("5a1d212bed3a552f0421fd6b"),
},
{
"unitHouseNo" : "Unit002",
"unitHouseType" : "CAT003",
"unitHouseStatus" : "SELL",
"_id" : ObjectId("5a1e3691af12544ff05690e3"),
}
]
}
],
}
Here is what I have queried so far, which i can get floor F2 that i wanted, but it came with both unit. I want only unitHouse with id : 5a1e3691af12544ff05690e3.
propertyDevModel.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId("5a17d305c438324308bffb19"),
}
},
{
$project: {
floorRow: {
$filter: {
input: '$floorRow',
as: 'floorRow',
cond: {
$eq: ['$$floorRow._id', mongoose.Types.ObjectId("5a1bdfbb4d63841c3cb6fc89")],
}
}
}
}
},
])
I have answered this q by myself, but i will keep this post for others who have the same problem.
db.aggregate([
{
$match: {
_id: projectId,
'floorRow._id': floorRowId
}
},
{$unwind: '$floorRow'},
{
$match: {
'floorRow._id': floorRowId
}
},
{$unwind: '$floorRow.unitHouse'},
{
$match: {
'floorRow.unitHouse._id': unitId
}
},
{
$project:{
'floorRow.unitHouse': 1
}
}
])

Resources