MongoDB : aggregate $project take array current item - node.js

Hi I have mongodb query like this .
.aggregate(
[
{ $match : { comment_box : "somdata" } },
{ $project : {
comment : 1,
created_at: 1,
current_date: "$replies.created_at",
},
dateDifference: { $subtract: [ new Date(), "$created_at"] }
}},
]
)
Imagine that I want to get created_at value from replies array in current date field. but this query returns
current_date: [
"2016-03-08T13:48:27.882Z",
"2016-03-08T14:26:22.194Z"
]
instead of current created_at value of each element from this array
I have tried many ways but got errors
current_date: "$replies.0.created_at",
current_date: "$replies.$.created_at",
current_date: "$$replies.created_at",
and etc
please help me to retrieve data like this
current_date:"2016-03-08T13:48:27.882Z",

I’m assuming you want the latest comment date. In that case, you can just take the $max:
current_date: { $max: '$replies.created_at' }
Demo:
> db.comments.insert({_id: 0, replies: [{created_at: new Date('2017-04-03')}, {created_at: new Date('2017-02-03')} ]})
WriteResult({ "nInserted" : 1 })
> db.comments.insert({_id: 1, replies: []})
WriteResult({ "nInserted" : 1 })
> db.comments.aggregate([{$project: {current_date: {$max: '$replies.created_at'}}}])
{ "_id" : 0, "current_date" : ISODate("2017-04-03T00:00:00Z") }
{ "_id" : 1, "current_date" : null }
Notice how the document with an empty replies array gets null.

Related

Mongoose get latest document for every unique value in field

I have a Mongoose schema which is like this:
const mySchema = new mongoose.Schema(
{
_id: String,
coin: String,
closeTime: Number,
volume: Number,
}
I have a bunch of different coins. Is it possible to get the latest document for each unique coin I have based on the closeTime?
Help would be much appreciated! Thanks.
You can use aggregation to sort by latest/newest closeTime, group by coin then getting the first document of that group:
mySchema.aggregate([
{ $sort: { closeTime: -1 } },
{ $group: { _id: "$coin", latest: { $first: "$$ROOT" } } }
])
This is sorting with numeric closeTime in descending order, getting the first/latest document and putting its data into a property called latest. This should create results like:
[{
"_id" : "2",
"latest" : {
"_id" : ObjectId("6149f106742bb30e2529c453"),
"coin" : "foo",
"closeTime" : 5,
"volume" : 1
}
}
{
"_id" : "1",
"latest" : {
"_id" : ObjectId("6149f111742bb30e2529c45f"),
"coin" : "bar",
"closeTime" : 4,
"volume" : 1
}
}]
You can take this one step further with other aggregation stages to extract/project the underlying coin document.

MongoDB aggregation, take an array of values and get their amount

this is my first time asking in StackOverflow and I hope I can explain what I'm aiming for.
I've got documents that look like this:
"_id" : ObjectId("5fd76b67a7e0fa652a297a9f"),
"type" : "play",
"session" : "5b0b5d57-c3ca-415f-8ef6-49bbd5805a23",
"episode" : 1,
"show" : 1,
"user" : 1,
"platform" : "spotify",
"currentTime" : 0,
"date" : ISODate("2020-12-14T13:40:51.906Z"),
"__v" : 0
}
I'd like to fetch for a show and group them by episode. I've got this far with my aggregattion:
const filter = { user, show, type: { $regex: /^(play|stop|close)$/ } }
const requiredFields = { "episode": 1, "session": 1, "date": 1, "currentTime": 1 }
// Get sessions grouped by episode
const it0 = {
_id: '$episode',
session:
{$addToSet:
{_id: "$session",
date:{$dateToString: { format: "%Y-%m-%d", date: "$date" }},
averageOfSession: {$cond: [ { $gte: [ "$currentTime", 0.1 ] }, "$currentTime", null ] }
},
},
count: { $sum: 1 }
}
// Filter unique sessions by session id and add them to a sessions field
const reduceSessions = {$addFields:
{sessions: {$reduce: {input: "$session",initialValue: [],in:
{$concatArrays: ["$$value",{$cond: [{$in: ["$$this._id","$$value._id"]},[],["$$this"]]}]}
}}}}
const projection = { $project: { _id: 0, episode: "$_id", plays: {$size: '$sessions'}, dropoff: {$avg: "$sessions.averageOfSession"}, sessions: '$session.date', events: "$count" } }
const arr = await Play.aggregate([
{ $match: filter }, {$project: requiredFields}, {$group: it0}, reduceSessions,
projection,{ $sort : { _id : 1 } }
])
and this is what my result looks like so far:
{
"episode": 5,
"plays": 4,
"dropoff": 3737.25,
"sessions": [
"2020-11-15",
"2020-11-15",
"2020-11-16",
"2020-11-15"
],
"events": 4
}...
What I'd like is for the 'sessions' array to be an object with one key for each distinct date which would contain the count, so something like this:
{
"episode": 5,
"plays": 4,
"dropoff": 3737.25,
"sessions": {
"2020-11-15": 3,
"2020-11-16": 1
},
"events": 4
}...
Hope that makes sense, thank you!!
You can first map sessions into key-value pairs. Then $group them to add up the sum. Then use $arrayToObject to convert to the format you want.
This Mongo playground is referencing this example.

MongoDB query to get the sum of all document's array field length

Below is the sample document of a collection, say "CollectionA"
{
"_id" : ObjectId("5ec3f19225701c4f7ab11a5f"),
"workshop" : ObjectId("5ebd37a3d33055331eb4730f"),
"participant" : ObjectId("5ebd382dd33055331eb47310"),
"status" : "analyzed",
"createdBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"updatedBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"results" : [
{
"analyze_by" : {
"user_name" : "m",
"user_id" : "5eb7aa24d33055331eb4728c"
},
"category_list" : [
"Communication",
"Controlling",
"Leading",
"Organizing",
"Planning",
"Staffing"
],
"analyzed_date" : ISODate("2020-05-19T14:48:49.993Z"),
}
],
"summary" : [],
"isDeleted" : false,
"isActive" : true,
"updatedDate" : ISODate("2020-05-19T14:48:50.827Z"),
"createdDate" : ISODate("2020-05-19T14:47:46.374Z"),
"__v" : 0
}
I need to query all the documents to get the "results" array length and return a sum of all document's "results" length.
For example,
document 1 has "results" length - 5
document 2 has "results" length - 6
then output should be 11.
Can we write a query, instead of getting all, iterating and the adding the results length??
If I had understand clearly you would like to project the length of the result attribute.
So you should check the $size operator would work for you.
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
You can use $group and $sum to calculate the total size of a field which contains the size of your results array. To create the field, You can use $size in $addFields to calculate the size of results in each document and put it the field. As below:
db.getCollection('your_collection').aggregate([
{
$addFields: {
result_length: { $size: "$results"}
}
},
{
$group: {
_id: '',
total_result_length: { $sum: '$result_length' }
}
}
])
You use an aggregation grouping query with $sum and $size aggregation operators to get the total sum of array elements size for all documents in the collection.
db.collection.aggregate( [
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
] )
Aggregation using Mongoose's Model.aggregate():
SomeModel.aggregate([
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
]).
then(function (result) {
console.log(result);
});

How can I update a subdocument in MongoDB?

Code created in Mongoose to update a subdocument was not working. So I tried to update the subdocument within the Mongo Shell.
This is the document (location) and subdocument (review):
{
"_id" : ObjectId("56d8c73314fbc7e702cfb8c4"),
"name" : "Costly",
"address" : "150, Super Street",
"coords" : [
-0.9630884,
51.451041
],
"reviews" : [
{
"author" : "kyle riggen1",
"_id" : ObjectId("56d8de74cc7f953efd8455d9"),
"rating" : 4,
"timestamp" : ISODate("2015-06-01T06:00:00Z"),
"reviewText" : "will the ID work?"
}
],
"rating" : 0,
"__v" : 2
}
Here are some of my attempts at updating the subdocument:
This question gave this format:
update({
_id: "56d8c73314fbc7e702cfb8c4",
"reviews._id": ObjectId("56d8de74cc7f953efd8455d9")
},{
$set: {"reviews.$.rating": 1}
}, false, true
);
This returned an error of "update is not defined" as shown:
2016-03-03T22:52:44.445-0700 E QUERY [thread1] ReferenceError: update is not defined :
#(shell):1:1
Which I think is because the command did not start with db.locations.update()
MongoDB documentation used this format:
db.locations.update(
{
_id: "56d8c73314fbc7e702cfb8c4",
review: { $elemMatch: { author: "kyle riggen1" } }
},
{ $set: { "location.$.rating" : 1 } }
)
This returns a valid update but the update didn't actually happen as shown:
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
This question used this format:
db.locations.update({
_id: "56d8c73314fbc7e702cfb8c4",
'review.author': 'kyle riggen1'
},
{ $set: { 'review.$.rating': 1 }}
)
This returns the same as the MongoDB documentation as shown here:
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
So these queries I guess are working but my data is not getting updated. Would my data be indexed wrong perhaps? The actual location is able to be updated even through a Mongoose API.
You can do it By $push Or $addToSet.
db.col.update(
{ name: 'reviews', 'list.id': 2 },
{$push: {'list.$.items': {id: 5, name: 'item5'}}}
)
See the reference from mongodb Manual
https://docs.mongodb.org/manual/reference/operator/update/push/
Please know db.collection.update( criteria, objNew, upsert, multi )
criteria: match condition
objNew: update content
upsert: true or false
true : if not existed, insert it
false : if not existed, don't insert
multi: true or false
true : update all matched documents
false : only update the first matched document

Group items by timeframe

I have a collection db.activities, each item of which has a dueDate. I need to present data in a following format, which basically a list of activities which are due today and this week:
{
"today": [
{ _id: 1, name: "activity #1" ... },
{ _id: 2, name: "activity #2" ... }
],
"thisWeek": [
{ _id: 3, name: "activity #3" ... }
]
}
I managed to accomplish this by simply querying for the last week's activities as a flat list and then grouping them with javascript on the client, but I suspect this is a very dirty solution and would like to do this on server.
look up mongo aggregation pipeline.
your aggregation has a match by date, group by date and a maybe a sort/order stage also by date.
lacking the data scheme it will be along the lines of
db.collection.aggregate([{ $match: {"duedate": { "$gte" : start_dt, "$lte" : end_dt} } ,
{ $group: {_id: "$duedate", recordid : "$_id" , name: "$name" },
{"$sort" : {"_id" : 1} } ] );
if you want 'all' records remove the $match or use { $match: {} } as one does with find.
in my opinion, you cannot aggregate both by day and week within one command. the weekly one may be achieved by projecting duedate using mongos $dayOfWeek. along the lines of
db.collection.aggregate([
{ $match: {"duedate": { "$gte" : start_dt, "$lte" : end_dt} } ,
{ $project : { dayOfWeek: { $dayOfWeek: "$duedate" } },
{ $group: {_id: "$dayOfWeek", recordid : "$_id" , name: "$name" },
{"$sort" : {"_id" : 1} } ] );
check out http://docs.mongodb.org/manual/reference/operator/aggregation/dayOfWeek/

Resources