Update all the key values of a dynamic object in MongoDB - node.js

I have a object which has dynamic keys all the values in that are numeric integers, i like to update all the key values in that object
{
"_id" : ObjectId("6395fc7b1c5a0c4a5fc9bd8e"),
"users" : [
ObjectId("638da89d0066308efe081709"),
ObjectId("63844feadf507942caaf90e3"),
ObjectId("638455e5fa983e9cf84c0f3f")
],
"type" : "GROUP",
"unReadCount" : {
"638da89d0066308efe081709" : 0,
"63844feadf507942caaf90e3" : 0,
"638455e5fa983e9cf84c0f3f" : 0
},
"createdAt" : ISODate("2022-12-11T21:21:23.815+05:30"),
"updatedAt" : ISODate("2022-12-11T22:48:33.953+05:30"),
"__v" : 0
},
I want to increment the unReadCount entire object values, please note the unReadCount object keys are not static it varies document to document. I tried with normal $inc operator it thrown error stating that has the field 'unReadCount' of non-numeric type object" $ wouldn't work as its not an array.
Please note that am trying to achieve this in MongoDB, i can do this via JS code by fetching the records and looping through it, but i like to do it in MongoDB/Mongoose. Any clue/help is appreciated

I think here is what you need
db.tests.updateMany({},[
{
$addFields: {
unReadCountArray: { $objectToArray: "$unReadCount" }
}
},
{
$addFields: {
unReadCountArray: {
$map: {
input: "$unReadCountArray",
as: "unReadCount",
in:
{
$mergeObjects: [ { k:"$$unReadCount.k", v: {$add: ["$$unReadCount.v", 1] }}, null ]
}
}
}
}
},
{
$addFields: {
unReadCount: {
$arrayToObject: '$unReadCountArray'
}
}
},{
$unset:'unReadCountArray'
},
{ $set: { modified: "$$NOW"} }])

Related

Mongoose, update values in array inside an object in an array

How can i update values in array inside an object in an array.
{
"_id" : ObjectId("63c7ca9584535c160ee4aaed"),
"status" : "REJECTED",
"steps" : [
{
"_id" : ObjectId("63c7ca9884535c160ee4ab03"),
"status" : "REJECTED",
"stepUsers" : [
{
"_id" : ObjectId("63c7ca9884535c160ee4ab04"),
"status" : "REJECTED",
}
]
},
]
}
I tried to update using arrayFilters but that didn't work. Mongo throw an error MongoServerError: Found multiple array filters with the same top-level field name steps
Collection.updateOne({
_id: id
}, {
$set: {
"steps.$[steps].stepUsers.$[stepUsers].status": 'PENDING',
}
}, {
arrayFilters: [
{ "steps._id": step._id },
{ "steps.stepUsers._id": stepUser._id }
]
})
I need to update steps.stepUsers.status in the collection.
Try to change the arrayFilters: "steps.stepUsers._id" -> "stepUsers._id"
since arrayFilters is referencing the string inside the [], not the path to it.
Collection.updateOne({
_id: id
},
{
$set: {
"steps.$[steps].stepUsers.$[stepUsers].status": "PENDING",
}
},
{
arrayFilters: [
{
"steps._id": step._id
},
{
"stepUsers._id": stepUser._id
}
]
})
See how it works on the playground example

Mongo aggregation $count pipeline to return count and objects [duplicate]

I want to perform an aggregation query that does basic pagination:
Find all orders that belongs to a certain company_id
Sort the orders by order_number
Count the total number of documents
Skips to e.g. document number 100 and passes on the rest
Limits the number of documents to e.g. 2 and passes them on
Finishes by returning the count and a selected few fields from the documents
Here is a breakdown of the query:
db.Order.collection.aggregate([
This finds all matching documents:
{ '$match' : { "company_id" : ObjectId("54c0...") } },
This sorts the documents:
{ '$sort' : { 'order_number' : -1 } },
This counts the documents and passes the unmodified documents, but I'm sure doing it wrong, because things turn weird from here:
{
'$group' : {
'_id' : null,
'count' : { '$sum' : 1 },
'entries' : { '$push' : "$$ROOT" }
}
},
This seems to skip some documents:
{ "$skip" : 100 },
This is supposed to limit the documents, but it does not:
{ "$limit" : 2 },
This does return the count, but it does not return the documents in an array, instead it returns arrays with each field:
{ '$project' : {
'count' : 1,
'entries' : {'_id' : "$entries._id", 'order_number' : "$entries.order_number"}
}
}
])
This is the result:
[
{ "_id" : null,
"count" : 300,
"entries" : [
{
"_id" : [ObjectId('5a5c...'), ObjectId('5a5c...')],
"order_number" : ["4346", "4345"]
},
{
"_id" : [ObjectId('5a5c...'), ObjectId('5a5c...')],
"order_number" : ["4346", "4345"]
},
...
]
}
]
Where do I get it wrong?
To calculate totals and return a subset, you need to apply grouping and skip/limit to the same dataset. For that you can utilise facets
For example to show 3rd page, 10 documents per page:
db.Order.aggregate([
{ '$match' : { "company_id" : ObjectId("54c0...") } },
{ '$sort' : { 'order_number' : -1 } },
{ '$facet' : {
metadata: [ { $count: "total" }, { $addFields: { page: NumberInt(3) } } ],
data: [ { $skip: 20 }, { $limit: 10 } ] // add projection here wish you re-shape the docs
} }
] )
It will return a single document with 2 fields:
{
"metadata" : [
{
"total" : 300,
"page" : 3
}
],
"data" : [
{
... original document ...
},
{
... another document ...
},
{
... etc up to 10 docs ...
}
]
}
Since mongoDB version 5.0 there is another option, that allows to avoid the disadvantage of $facet, the grouping of all returned document into a one big document. The main concern is that a document as a size limit of 16M. Using $setWindowFields allows to avoid this concern:
db.Order.aggregate([
{$match: {company_id: ObjectId("54c0...") } },
{$sort: {order_number: -1 } },
{$setWindowFields: {output: {totalCount: {$count: {}}}}}
{$skip: 20 },
{$limit: 10 }
])

Mogodb Aggregation

This is my user collection
{
"_id" : ObjectId("58e8cb640f861e6c40627a06"),
"actorId" : "665991",
"login" : "petroav",
"gravatar_id" : "",
"url" : "https://api.github.com/users/petroav",
"avatar_url" : "https://avatars.githubusercontent.com/u/665991?"
}
This is my repo collection
{
"_id" : ObjectId("58e8cb640f861e6c40627a07"),
"repoId" : "28688495",
"name" : "petroav/6.828",
"url" : "https://api.github.com/repos/petroav/6.828"
}
This is my events collections
{
"_id" : ObjectId("58e8cb640f861e6c40627a08"),
"eventId" : "2489651045",
"type" : "CreateEvent",
"actorLogin" : "petroav",
"repoId" : "28688495",
"eventDate" : ISODate("2015-01-01T15:00:00.000+0000"),
"public" : true
}
I am trying to do following queries on above data
Return list of all repositories with their top contributor
Find the repository with the highest number of events from an actor (by login). If multiple repos have the same number of events, return the one with the latest event.
Return actor details and list of contributed repositories by login
I tried 3 one by doing this
db.events.aggregate(
[ {
$match:{"actorLogin":"petroav"}
},
{
$lookup:{
from:"repos",
localField:"repoId",
foreignField:"repoId",
as:"Repostory"
}
},
{
$group:{ _id : "$Repostory", repo: { $push: "$$ROOT" } }
}
]
).pretty()
Please help. I am new to mongodb.
These should work, you may have to update some of the variable names if they don't match your code exactly. Because you are using actorLogin and repoId as references instead of _id, you likely want to create indexes for the fields to help with performance.
Also you may want to add a $project stage at the end of these pipelines if you want to clean up the final formats, remove extra fields, rename fields, etc..
For Number 1
db.repos.aggregate(
[
{
$lookup:{
from:"events",
localField:"repoId",
foreignField:"repoId",
as:"Event"
}
},{
$unwind:"$Event"
},
{
$group:{
_id : {repo: "$_id", user: "$Event.actorLogin" },
contributionCount: { $sum:1 },//number of times logged in
}
},
{
$sort: {
contributionCount: -1
}
},{
$group:{
_id: {repo:'$_id.repo'},
contributionCount: {$first: '$contributionCount' },
actorLogin: {$first: '$_id.user' }
}
}
]
).then(console.log)
For Number 2
db.events.aggregate(
[ {
$match:{"actorLogin":"petroav"}
},
{
$lookup:{
from:"repos",
localField:"repoId",
foreignField:"repoId",
as:"Repostory"
}
},{
$unwind:"$Repostory"
},
{
$group:{
_id : "$Repostory",
loginCount: { $sum:1 },//number of times logged in
lastLoginDate: {$max:'$eventDate'} //largest ISODate for the repo
}
},
{
$sort: {
loginCount: -1,
date: -1
}
},
{limit:1}
]
).then(console.log)
For number 3
db.user.aggregate(
[
{
$match:{"actorLogin":"petroav"}
},
{
$lookup:{
from:"events",
localField:"actorLogin",
foreignField:"actorLogin",
as:"Events"
}
},{
$unwind:"$Events"
},
{
$lookup:{
from:"repos",
localField:"Events.repoId",
foreignField:"repoId",
as:"Repostory"
}
},{
$unwind:"$Repostory"
},{
$group: {
_id:'$actorLogin',
user: {$first:'$$ROOT'}
repos: {$addToSet:'$Repostory'}
}
}
]
).then(console.log)

Search for most common "data" with mongoose, mongodb

My structure.
User:
{
name: "One",
favoriteWorkouts: [ids of workouts],
workouts: [ { name: "My workout 1" },...]
}
I want to get list of favorits/hottest workouts from database.
db.users.aggregate(
{ $unwind : "$favorite" },
{ $group : { _id : "$favorite" , number : { $sum : 1 } } },
{ $sort : { number : -1 } }
)
This returns
{
"hot": [
{
"_id": "521f6c27145c5d515f000006",
"number": 1
},
{
"_id": "521f6c2f145c5d515f000007",
"number": 1
},...
]}
But I want
{
hot: [
{object of hottest workout 1, object of hottest workout 2,...}
]}
How do you sort hottest data and fill the result with object, not just ids?
You are correct to want to use MongoDB's aggregation framework. Aggregation will give you the output you are looking for if used correctly. If you are looking for just a list of the _id's of all users' favorite workouts, then I believe that you would need to add an additional $group operation to your pipeline:
db.users.aggregate(
{ $unwind : "$favoriteWorkouts" },
{ $group : { _id : "$favoriteWorkouts", number : { $sum : 1 } } },
{ $sort : { number : -1 } },
{ $group : { _id : "oneDocumentWithWorkoutArray", hot : { $push : "$_id" } } }
)
This will yield a document of the following form, with the workout ids listed by popularity:
{
"_id" : "oneDocumentWithWorkoutArray",
"hot" : [
"workout6",
"workout1",
"workout5",
"workout4",
"workout3",
"workout2"
]
}

How to build query for relating parent document's field to embedded array document's field. Using MongoDB aggregation Operators

Consider the following document in the collection named 'CityAssociation'
{
"_id" : "MY_ID",
"ThisCityID" : "001",
"CityIDs" : [{
"CityID" : "001",
"CityName" : "Bangalore"
}, {
"CityID" : "002",
"CityName" : "Mysore"
}],
"CityUserDetails": {
"User" : "ABCD"
}
}
Now I have User value i.e. in above case I have value ABCD and want to find it with only city where the first level's field ThisCityID matches to the embedded array documnet's field CityID. Finally I need to project as follows (for the above case):
{
'UserName': 'ABCD',
'HomeTown':'Bangalore'
}
In Node.js + MongoDB native drive, I wrote a aggregation query as follows which is not working as expected.
collection.aggregate([
{ $match: { 'CityUserDetails.User': 'ABCD', 'CityIDs': { $elemMatch: { CityID: ThisCityID}}} },
{ $unwind: "$CityIDs" },
{ $group: {
_id: '$_id',
CityUserDetails: { $first: "$CityUserDetails" },
CityIDs: { $first: "$CityIDs" }
}
},
{ $project: {
_id: 0,
"UserName": "$CityUserDetails.User",
"HomeTown": "$CityIDs.CityName"
}
}
], function (err, doc) {
if (err) return console.error(err);
console.dir(doc);
}
);
Can anyone tell me how this can be done with query.
Note: On MongoDB schema we don't have control to change it.
You can use the $eq operator to check if the first level's field ThisCityID matches embedded array document's field CityID.
db.city.aggregate([
{ $match : { "CityUserDetails.User" : "ABCD" }},
{ $unwind : "$CityIDs" },
{ $project : {
matches : { $eq: ["$CityIDs.CityID","$ThisCityID"]},
UserName : "$CityUserDetails.User",
HomeTown : "$CityIDs.CityName"
}},
{ $match : { matches : true }},
{ $project : {
_id : 0,
UserName : 1,
HomeTown : 1
}},
])
And the result is:
{
"result" : [
{
"UserName" : "ABCD",
"HomeTown" : "Bangalore"
}
],
"ok" : 1
}

Resources