How can I group a field in subdocument in MongoDB - arrays

I have the following document:
{
"_id": "5a5cae1890254804e3a4655d",
"additional_ingredient": {
"concentration": 40.0,
"formulations": [
"5a1826c664f7fe04afbe9198"
]
}
}
Inside additional_ingredient there is an array field formulations. When I have an array field in the first level of a document I can use $unwind then $lookup to join an external document and the group by this field.
But I cannot do the same with a nested array. What is the best solution for that?
What I expect:
{
"_id": "5a5cae1890254804e3a4655d",
"additional_ingredient": {
"concentration": 40.0,
"formulations": [
{
"_id": "5a1826c664f7fe04afbe9198",
"date_created": "Fri, 24 Nov 2017 14:03:50 GMT",
"date_modified": "Fri, 24 Nov 2017 14:03:50 GMT",
}
]
}
}
I have a problem with grouping. The field name 'additional_ingredient.formulations' cannot contain '.'
This is a part of the query:
{"$unwind": "$additional_ingredient.formulations"},
self.__ADDITIONAL_INGREDIENT_FORMULATION_LOOKUP_QUERY,
{"$unwind": "$additional_ingredient.formulations"},
{"$group": {
"_id": "$_id",
"additional_ingredient.formulations": {"$push": "$additional_ingredient.formulations"},
}},

Related

Sorting an array of objects in MongoDB collection

I have a collection in a MongoDB with a document like below:
{
"_id": "63269e0f85bfd011e989d0f7",
"name": "Aravind Krishna",
"mobile": 7309454620,
"email": "akaravindk59#gmail.com",
"password": "$2b$12$0dOE/0wj6uX604h3DZpGxuO/L.fZg7KCm7mOGsNMkarSaeG2C/Wvq",
"orders": [
{
"paymentIntentId": "pi_3LjFDtSHVloG65Ul0exLkzsO",
"cart": [array],
"amount": 3007,
"created": 1663475717,
"_id": "6326a01344f26617fc1a65d6"
},
{
"paymentIntentId": "pi_3LjFFUSHVloG65Ul1FQHlZ9H",
"cart": [array],
"amount": 389,
"created": 1663475816,
"_id": "6326a07744f26617fc1a65d8"
}
],
"__v": 0
}
I wanted to get only the orders array sorted by the created property in both ascending and descending manner. As we can see here that orders field inside this document is an array of objects. Please give a solution if you have one. I tried the sortArray method but it is giving an error. Please help.
For some assistance the output should look something like this:
{
"_id": "63269e0f85bfd011e989d0f7",
"orders": [
{
"paymentIntentId": "pi_3LjFDtSHVloG65Ul0exLkzsO",
"cart": [array],
"amount": 3007,
"created": 1663475717,
"_id": "6326a01344f26617fc1a65d6"
},
{
"paymentIntentId": "pi_3LjFFUSHVloG65Ul1FQHlZ9H",
"cart": [array],
"amount": 389,
"created": 1663475816,
"_id": "6326a07744f26617fc1a65d8"
}
]
}
See, I got only the orders field but I want it sorted by the created property value both ascending and descending.
As you mentioned the best way to achieve this is to use $sortArray, I'm assuming the error you're getting is a version mismatch as this operator was only recently added at version 5.2.
The other way to achieve the same result as not as pleasant, you need to $unwind the array, $sort the results and then $group to restore structure, then it's easy to add the "other" order by using $reverseArray although I recommend instead of duplicating your data you just handle the "reverse" requirement in code, overall the pipeline will look like so:
db.collection.aggregate([
{
$unwind: "$orders"
},
{
$sort: {
"orders.created": 1
}
},
{
$group: {
_id: "$_id",
asc_orders: {
$push: "$orders"
}
}
},
{
$addFields: {
desc_orders: {
"$reverseArray": "$asc_orders"
}
}
}
])
Mongo Playground

Mongo query Update year depends on the inner document field

Here is my data. I wanted to change year but It should effective to only the first item of the document array
{
"_id": {
"$oid": "62053aa8aa1cfbe8c4e72662"
},
"school": "Test",
"reports": [
{
"year": "2020", // This has to be changed to 2019
"createdAt": {
"$date": "2022-02-10T17:05:25.682Z"
},
"pid": {
"$oid": "620545d5097761628f32365a"
},
"details": {
"end_date": {
"$date": "2020-03-31T00:00:00.000Z" // when end date is prior to July 01 of the $year mentioned.
}
}
}, {
"year": "2020",
"createdAt": {
"$date": "2022-03-14T19:08:38.125Z"
},
"pid": {
"$oid": "622f92b68a408531d4b784de"
},
"details": {
"end_date": {
"$date": "2021-03-31T00:00:00.000Z"
}
}
}
]
}
In the above data, I want to reduce the year to the previous year, if details.end_date is prior to July 01 of the current mentioned year. But It should change only the first item of the embedded array.
For example,
If Year is 2020 and details.end_date is prior to 01-July-2020, then change the year to 2019
If Year is 2020 and details.end_date is after 01-July-2020, then do not change the year
If Year is 2021 and details.end_date is prior to 01-July-2021, then change the year to 2020
If Year is 2021 and details.end_date is after 01-July-2021, then do not change the year
You can do the followings in an aggregation pipeline:
isolate the first elem of the reports array for easier processing using $arrayElemAt
use $cond to derive the year value with $month. Use $toInt and $toString for type conversion
use $concatArrays to append back the processed first Elem back to the reports array. Keep only the "tail" (i.e. without the first elem) using $slice
$merge to update the result back to the collection
db.collection.aggregate([
{
"$addFields": {
"firstElem": {
"$arrayElemAt": [
"$reports",
0
]
}
}
},
{
"$addFields": {
"firstElem.year": {
"$cond": {
"if": {
$lt: [
{
"$month": "$firstElem.end_date"
},
7
]
},
"then": {
"$toString": {
"$subtract": [
{
"$toInt": "$firstElem.year"
},
1
]
}
},
"else": "$firstElem.year"
}
}
}
},
{
"$addFields": {
"reports": {
"$concatArrays": [
[
"$firstElem"
],
{
"$slice": [
"$reports",
1,
{
"$subtract": [
{
"$size": "$reports"
},
1
]
}
]
}
]
}
}
},
{
"$project": {
firstElem: false
}
},
{
"$merge": {
"into": "collection",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

MongoDB remove elements depending on element before (Iterating)

I want to remove ($unset) elements from my MongoDB Objects with condition if the same Object has a similar element.
My Object:
{
"_id": "5eabf8b144345b36b00bfbaa",
"ranktime": [{
"pos":"2",
"datum":"Mon May 05 2020 12:22:52 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"1",
"datum":"Fri May 01 2020 12:23:10 GMT+0200 (GMT+02:00)",
"source":"SOURCE1"
},{
"pos":"37",
"datum":"Fri May 01 2020 12:25:14 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"12",
"datum":"Fri May 01 2020 12:25:14 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"37",
"datum":"Fri May 01 2020 18:45:27 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
}]
}
So I want to remove the entry in ranktime if ranktime.source == "SOURCE2" and if the date is the same as with the object before. Actually I have to iterate through the single elements of ranktime. Is this possible in MongoDB ?
The Expected outcome would be:
{
"_id": "5eabf8b144345b36b00bfbaa",
"ranktime": [{
"pos":"2",
"datum":"Mon May 05 2020 12:22:52 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"1",
"datum":"Fri May 01 2020 12:23:10 GMT+0200 (GMT+02:00)",
"source":"SOURCE1"
},{
"pos":"37",
"datum":"Fri May 01 2020 12:25:14 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
}]
}
Basically you can use $reduce to process an array and define previous element using $let and $arrayElemAt statements. The new $set syntax allows you to use aggregation within update statement:
db.col.updateMany({}, [
{
$set: {
ranktime: {
$reduce: {
input: "$ranktime",
initialValue: [],
in: {
$let: {
vars: { last: { $arrayElemAt: [ "$$value", -1 ] } },
in: {
$cond: [
{
$and: [
{ "$eq": [ "$$last.source", "SOURCE2" ] },
{ "$eq": [ { $substr: [ "$$last.datum", 0, 15 ] }, { $substr: [ "$$this.datum", 0, 15 ] } ] },
]
},
"$$value",
{ $concatArrays: [ "$$value", [ "$$this" ] ] }
]
}
}
}
}
}
}
}
])
Aggregation Example

Unable to update nested array in mongodb using nodeJs, and no error throwing

I need to update nested array in mongodb using nodejs. I have tried like this but not helped and even its not throwing error.
My Collection is this
{
"country_details": [
{
"cities": [
"Abbeville"
],
"_id": "5a6ec189bb68bb09105eabe8",
"countryCode": "US",
"countryName": "United States"
}
],
"deletion_indicator": "N",
"_id": "5a6ec189bb68bb09105eabe7",
"date_created": "Mon Jan 29 2018 12:09:05 GMT+0530 (India Standard Time)",
"__v": 0
}
and I want to update cities, And I have try like one
let query = {_id: "5a6ec189bb68bb09105eabe7", countryCode: "US", countryName: "United States"};
countryAndCitiesModel.collection.update(
query,
{$push: {"country_details.$.cities": "ABC"}},
(err, result)=> {
if(err){
return next(err);
}else{
return next(result);
}
});
not getting update, but getting result like is
{
"ok": 1,
"nModified": 0,
"n": 0
}
Please help.. Thanks
You're trying to match countryCode and countryName which are properties of nested object country_details and that's why your query can't match any document. To fix that you should fix paths to your fields:
db.collection.update(
{_id: "5a6ec189bb68bb09105eabe7", "country_details.countryCode": "US", "country_details.countryName": "United States"},
{$push: {"country_details.$.cities": "ABC"}})

Comparing an element's field in array with a field in MongoDB

I have a collection Group like this:
{
"_id" : ObjectId("5822dd5cb6a69ca404e0d93c"),
"name" : "GROUP 1",
"member": [
{
"_id": ObjectId("5822dd5cb6a69ca404e0d93d")
"user": ObjectId("573ac820eb3ed3ea156905f6"),
"task": ObjectId("5822ddecb6a69ca404e0d942"),
},
{
"_id": ObjectId("5822dd5cb6a69ca404e0d93f")
"user": ObjectId("57762fce5ece6a5d04457bf9"),
"task": ObjectId("5822ddecb6a69ca404e0d943"),
}
],
curTask: {
"_id": ObjectId("5822ddecb6a69ca404e0d942"),
"time": ISODate("2016-01-01T01:01:01.000Z")
}
}
{
"_id" : ObjectId("573d5ff8d1b7b3b32e165599"),
"name" : "GROUP 2",
"member": [
{
"_id": ObjectId("574802e031e70b503eabe195")
"user": ObjectId("573ac820eb3ed3ea156905f6"),
"task": ObjectId("5775f1a74b41037e246a51d1"),
},
{
"_id": ObjectId("574802e031e70b503eabe198")
"user": ObjectId("573ac79beb3ed3ea156905f4"),
"task": ObjectId("576cfa042c0a4054794dd242"),
}
],
curTask: {
"_id": ObjectId("577249a2f9dba0c750ef705b"),
"time": ISODate("2016-01-01T01:01:01.000Z")
}
}
{
"_id" : ObjectId("574802e031e70b503eabe194"),
"name" : "GROUP 3",
"member": [
{
"_id": ObjectId("574be0a2bf16234f5a752f83")
"user": ObjectId("573ac79beb3ed3ea156905f4"),
"task": ObjectId("5822ddecb6a69ca404e0d942"),
},
{
"_id": ObjectId("574d397d6e9f07d64d1e4e40")
"user": ObjectId("57762fce5ece6a5d04457bf9"),
"task": ObjectId("5822ddecb6a69ca404e0d943"),
}
],
curTask: {
"_id": ObjectId("5822ddecb6a69ca404e0d942"),
"time": ISODate("2016-01-01T01:01:01.000Z")
}
}
And I want to be able to find all group where user with objectId 573ac820eb3ed3ea156905f6 (1st user in group 1) do not do the same task as currentTask. So far I've wrote this query:
db.getCollection('groups').find({"member":{ "$elemMatch": {"user": ObjectId("573ac820eb3ed3ea156905f6")
, "task": { "$ne":"this.curTask._id"}}}})
But this didn't seem to work as it still return the group where user 573ac820eb3ed3ea156905f6 having his task === curTask._id. The first half of elemMatch seem to work fine (only find group with user with objectid 573ac820eb3ed3ea156905f6 in member, the query only return group 1 and 2 since group 3 don't have that user.) but I cant seem to make mongodb compare a field in the object of the array with another field of the document. Anyone have any idea how do I make this comparison?
There are two solutions to the problem -
First - Using $where. By using $where you can use Javascript code inside mongodb queries. Makes the code flexible, but the shortcoming is that it runs slow since Javascript code has to run rather than more optimized mongoDB C++ code.
db.getCollection('groups').find({
$where: function () {
var flag = 0;
for(var i=0; i<obj.member.length;i++) {
if(obj.member[i].user.str == ObjectId("573ac820eb3ed3ea156905f6").str && obj.member[i].task.str != obj.curTask._id.str ){flag = 1; break;}
}
return flag;
}
})
Second - Using an aggregation pipeline. Here I am unwinding the array, doing matches as described, and finally recreating the array as it was needed. If the not matching elements in the member array are not needed, one can omit the last grouping part.
[
{$match: {'member.user': ObjectId("573ac820eb3ed3ea156905f6")}},
{$unwind: '$member'},
{$project: {
name: 1,
member: 1,
curTask: 1,
ne: {$and: [{$ne: ['$member.task', '$curTask._id']}, {$eq: ['$member.user', ObjectId("573ac820eb3ed3ea156905f6")]}]}
}},
{$group: {
_id: '$_id',
member: {$push: '$member'},
curTask: {$first: '$curTask'},
name: {$first: '$name'},
check: {$sum: {$cond: ['$ne', 1, 0]}}
}},
{$match: {check: {$gt: 0}}}
]

Resources