Mongo query how to retrieve the latest inserted array value? - mongodb

I have a mongodb collection which contains some array values such as ActivityType, Note and ActivityDate. The array name is called activities. I need to rename some fields so I used aggregate and $project to rename some columns for the output. But I only need to return the latest inserted ActivityDate for the array value.
My current query returns all the array value in the Activity array:
db.test.aggregate([
{$match: {}
}, {$unwind: "$activities"},
{$match: {}},
{ "$project": {
"_id" : 0,
"Project Number": "$ProjectNumber" ,
"Activity Type": "$activities.activityTypeDesc" ,
"Date of Activity": {
"$dateToString": { "format": "%Y-%m-%d", "date": "$activities.dateOfActivity" }
}
}}
])
It is sort of like getting the top 1 order by in sql server. How do I do that in Mongodb? After some reading seems like I need to use $sort and $group, but I don't know how to fit in here.
I have some sample data below:
{
"_id" : ObjectId("5fd289a93f7cf02c36837ca7"),
"ProjectNumber" : "ABC1234567",
"activities" : [
{
"activityTypeDesc" : "Type1",
"dateOfActivity" : ISODate("2021-02-20T06:00:00.000Z"),
"activityNote" : ""
},
{
"activityTypeDesc" : "Type2",
"dateOfActivity" : ISODate("2021-03-04T06:00:00.000Z"),
"activityNote" : ""
},
{
"activityTypeDesc" : "Type3",
"dateOfActivity" : ISODate("2021-01-04T06:00:00.000Z"),
"activityNote" : ""
},
{
"activityTypeDesc" : "Type4",
"dateOfActivity" : ISODate("2021-04-15T05:00:00.000Z"),
"activityNote" : ""
}
]
}
{
"_id" : ObjectId("5fd2ca65d1a01d157c0179be"),
"ProjectNumber" : "12345",
"activities" : []
}
The result of the query should return two rows, one with the lastest activitydate , one with no activitydate (as no array value)
Any help will be appreciated!

$unwind deconstruct activities array
$sort by dateOfActivity in descending order
$group by _id and get first activity required fields
db.collection.aggregate([
{
$unwind: {
path: "$activities",
preserveNullAndEmptyArrays: true
}
},
{ $sort: { "activities.dateOfActivity": -1 } },
{
$group: {
_id: "$_id",
"Project Number": { $first: "$ProjectNumber" },
"Activity Type": { $first: "$activities.activityTypeDesc" },
"Date Of Activity": {
$first: {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$activities.dateOfActivity"
}
}
}
}
}
])
Playground

Related

MongoDB - Filter Array and Get distinct counts

My MongoDB document looks like below:
{
"_id" : ObjectId("5fb1828a6dbd2e5c533e2378"),
"email" : "hskasd#gmail.com",
"fname" : "JOSE",
"appt" : [
{
"date" : "12/04/2020",
"time" : "0900",
},
{
"date" : "12/05/2020",
"time" : "1000",
},
]
}
Both appt.date and appt.time are String!
I need to filter the records that contain array value appt.date: "12/04/2020". Then find all distinct appt.time values for given date along with its count.
I tried to use the pipeline aggregation but just cannot get it to work. How can I solve this in MongoDB 2.6.11?
You can try,
$match appt.date condition to filter main document
$unwind deconstruct appt array
$match appt.date condition again to filter sub document
$group by null and make time unique using $addToSet array
$addFields to get count of total time
db.collection.aggregate([
{ $match: { "appt.date": "12/04/2020" } },
{ $unwind: "$appt" },
{ $match: { "appt.date": "12/04/2020" } },
{
$group: {
_id: null,
time: { $addToSet: "$appt.time" }
}
},
{
$project: {
_id: 0,
time: 1,
count: { $size: "$time" }
}
}
])
Playground

Need to sum from array object value in mongodb

I am trying to calculate total value if that value exits. But query is not working 100%. So can somebody help me to solve this problem. Here my sample document. I have attached two documents. Please these documents & find out best solution
Document : 1
{
"_id" : 1"),
"message_count" : 4,
"messages" : {
"data" : [
{
"id" : "11",
"saleValue": 1000
},
{
"id" : "112",
"saleValue": 1400
},
{
"id" : "22",
},
{
"id" : "234",
"saleValue": 111
}
],
},
"createdTime" : ISODate("2018-03-18T10:18:48.000Z")
}
Document : 2
{
"_id" : 444,
"message_count" : 4,
"messages" : {
"data" : [
{
"id" : "444",
"saleValue" : 2060
},
{
"id" : "444",
},
{
"id" : 234,
"saleValue" : 260
},
{
"id" : "34534",
}
]
},
"createdTime" : ISODate("2018-03-18T03:11:50.000Z")
}
Needed Output:
{
total : 4831
}
My query :
db.getCollection('myCollection').aggregate([
{
"$group": {
"_id": "$Id",
"totalValue": {
$sum: {
$sum: "$messages.data.saleValue"
}
}
}
}
])
So please if possible help me to solve this problem. Thanks in advance
It's not working correctly because it is aggregating all the documents in the collection; you are grouping on a constant "_id": "tempId", you just need to reference the correct key by adding the $ as:
db.getCollection('myCollection').aggregate([
{ "$group": {
"_id": "$tempId",
"totalValue": {
"$sum": { "$sum": "$messages.data.saleValue" }
}
} }
])
which in essence is a single stage pipeline version of an aggregate operation with an extra field that holds the sum expression before the group pipeline then calling that field as the $sum operator in the group.
The above works since $sum from MongoDB 3.2+ is available in both the $project and $group stages and when used in the $project stage, $sum returns the sum of the list of expressions. The expression "$messages.data.value" returns a list of numbers [120, 1200] which are then used as the $sum expression:
db.getCollection('myCollection').aggregate([
{ "$project": {
"values": { "$sum": "$messages.data.value" },
"tempId": 1,
} },
{ "$group": {
"_id": "$tempId",
"totalValue": { "$sum": "$values" }
} }
])
You can add a $unwind before your $group, in that way you will deconstructs the data array, and then you can group properly:
db.myCollection.aggregate([
{
"$unwind": "$messages.data"
},
{
"$group": {
"_id": "tempId",
"totalValue": {
$sum: {
$sum: "$messages.data.value"
}
}
}
}
])
Output:
{ "_id" : "tempId", "totalValue" : 1320 }
db.getCollection('myCollection').aggregate([
{
$unwind: "$messages.data",
$group: {
"_id": "tempId",
"totalValue": { $sum: "$messages.data.value" }
}
}
])
$unwind
According to description as mentioned into above question, as a solution please try executing following aggregate query
db.myCollection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: '$messages.data'
}
},
// Stage 2
{
$group: {
_id: {
pageId: '$pageId'
},
total: {
$sum: '$messages.data.saleValue'
}
}
},
// Stage 3
{
$project: {
pageId: '$_id.pageId',
total: 1,
_id: 0
}
}
]
);
You can do it without using $group. Grouping made other data to be managed and addressed. So, I prefer using $sum and $map as shown below:
db.getCollection('myCollection').aggregate([
{
$addFields: {
total: {
$sum: {
$map: {
input: "$messages.data",
as: "message",
in: "$$message.saleValue",
},
},
},
},
},
}
])

Filter $lookup results

I have 2 collections (with example documents):
reports
{
id: "R1",
type: "xyz",
}
reportfiles
{
id: "F1",
reportid: "R1",
time: ISODate("2016-06-13T14:20:25.812Z")
},
{
id: "F14",
reportid: "R1",
time: ISODate("2016-06-15T09:20:29.809Z")
}
As you can see one report may have multiple reportfiles.
I'd like to perform a query, matching a report id, returning the report document as is, plus an additional key storing as subdocument the reportfile with the most recent time (even better without reportid, as it would be redundant), e.g.
{
id: "R1",
type: "xyz",
reportfile: {
id: "F14",
reportid: "R1",
time: ISODate("2016-06-15T09:20:29.809Z")
}
}
My problem here is that every report type has its own set of properties, so using $project in an aggregation pipeline is not the best way.
So far I got
db.reports.aggregate([{
$match : 'R1'
}, {
$lookup : {
from : 'reportfiles',
localField : 'id',
foreignField : 'reportid',
as : 'reportfile'
}
}
])
returning of course as ´reportfile´ the list of all files with the given reportid. How can I efficiently filter that list to get the only element I need?
efficiently -> I tried using $unwind as next pipeline step but the resulting document was frighteningly and pointlessly long.
Thanks in advance for any suggestion!
You need to add another $project stage to your aggregation pipeline after the $lookup stage.
{ "$project": {
"id": "R1",
"type": "xyz",
"reportfile": {
"$let": {
"vars": {
"obj": {
"$arrayElemAt": [
{ "$filter": {
"input": "$reportfile",
"as": "report",
"cond": { "$eq": [ "$$report.time", { "$max": "$reportfile.time" } ] }
}},
0
]
}
},
"in": { "id": "$$obj.id", "time": "$$obj.time" }
}
}
}}
The $filter operator "filter" the $lookup result and return an array with the document that satisfy your condition. The condition here is $eq which return true when the document has the $maximum value.
The $arrayElemAt operator slice the $filter's result and return the element from the array that you then assign to a variable using the $let operator. From there, you can easily access the field you want in your result with the dot notation.
What you would require is to run the aggregation operation on the reportfile collection, do the "join" on the reports collection, pipe a $group operation to ordered (with $sort) and flattened documents (with $unwind) from the $lookup pipeline. The preceding result can then be grouped by the reportid and output the desired result using the $first accumulator aoperators.
The following demonstrates this approach:
db.reportfiles.aggregate([
{ "$match": { "reportid": "R1" } },
{
"$lookup": {
"from": 'reports',
"localField" : 'reportid',
"foreignField" : 'id',
"as": 'report'
}
},
{ "$unwind": "$report" },
{ "$sort": { "time": -1 } },
{
"$group": {
"_id": "$reportid",
"type": { "$first": "$report.type" },
"reportfile": {
"$first": {
"id": "$id",
"reportid": "$reportid",
"time": "$time"
}
}
}
}
])
Sample Output:
{
"_id" : "R1",
"type" : "xyz",
"reportfile" : {
"id" : "F14",
"reportid" : "R1",
"time" : ISODate("2016-06-15T09:20:29.809Z")
}
}

MongoDB - get $max among fields at different levels

I have a MongoDB collection with documents of this (simplified) form
{
"_id": "Doc"
"created": NumberLong("1422526079335")
}
Additionally, this documents may have an additional edited field
{
"_id": "Doc"
"created": NumberLong("1422526079335")
"edited": {
"date": NumberLong("1458128507498")
}
}
What I need is to get the most recent timestamp (among created and edited.date) for a subset of these documents, matching certain conditions.
What I achieved so far is to get the most recent created timestamp
db.myCollection.aggregate([ { $match: { ... } },
{ $project: { _id:0, created: 1 } },
{ $group: { _id: 'latest', latest: { $max: '$created' } } }
])
which returns
{ "_id" : "latest", "latest" : NumberLong("1422526079335") }
How can I integrate the check against edited.date in the $max logic above? Or alternatively is there another solution? Thanks is advance!
Try this script. it's simple $max operator.
I have following documents in collection
{
"_id" : "Doc",
"created" : NumberLong(1422526079335),
"edited" : {
"date" : NumberLong(1458128507498)
}
}
{
"_id" : "Doc1",
"created" : NumberLong(1422526079335)
}
Try running following query:
db.doc.aggregate([
{
$match: { ... }
},
{
$project:{
latest:{ $max:["$created", "$edited.date"]}
}
}
])
Output will be:
{
"_id" : "Doc",
"latest" : NumberLong(1458128507498)
}
{
"_id" : "Doc1",
"latest" : NumberLong(1422526079335)
}
you can use $cond in last $project pipe - lastModifedDate makes the trick :-)
db.ill.aggregate([{
$project:{
lastModifedDate:{
$cond: {
if: { $gte: [ "$created", "$edited.date"] },
then: "$created", else: "$edited.date" }
}}}])

mongodb sorting array documents

This is my document i want to sort array documents by ascending order to get so for that my queries are in following code.but i am not getting the docs in sorted way.
The query is
db.sample.find({_id: ObjectId("55b32f5957e47fabd30c5d2e")}).sort({'naresh.ts':1}).pretty();
This is the result I am getting
{
"_id" : ObjectId("55b32f5957e47fabd30c5d2e"),
"naresh" : [
{
"ts" : "hi",
"created_by" : 1437806425105
},
{
"ts" : "hello",
"created_by" : 1437806425105
},
{
"ts" : "waht",
"created_by" : 1437807757261
},
{
"ts" : "lefo",
"created_by" : 1437807768514
},
{
"ts" : "lefow",
"created_by" : 1437807775719
}
]
}
You can use $aggregation like following query:
db.collection.aggregate({
"$match": {
"_id": ObjectId("55b32f5957e47fabd30c5d2e")
}
}, {
$unwind: "$naresh"
}, {
$sort: {
"naresh.ts": 1
}
}, {
"$group": {
_id: "$_id",
"naresh": {
$push: "$naresh"
}
}
})
The cursor .sort() only looks at the values in the array to decide to use the "smallest" value of the specified field ( in ascending order ) to determine how to "sort" the documents in the response. This does not "sort" the array content itself.
In order to sort the array, you need to use the aggregation framework to manipulate the document:
db.sample.aggregate([
{ "$match": { "_id": ObjectId("55b32f5957e47fabd30c5d2e") },
{ "$unwind": "$naresh" },
{ "$sort": { "$naresh.ts": 1 } },
{ "$group": {
"_id": "$_id",
"naresh": { "$push": "$naresh" }
}}
])
That sorts the array.
Better yet, if you "always" want then results sorted then do it as you update the document:
db.sample.update({},{ "$push": { "$each": [], "$sort": { "ts": 1 } } },{ "multi": true })
And use those same, $each and $sort modifiers when adding new elements to the array and the content will remain sorted.
If you want just query the collection and get the output sorted, then Blackes Seven's answer will work perfectly for you.
However if you want to update the documents in the sorted order, go with this update query:
update(
{
_id: ObjectId("55b32f5957e47fabd30c5d2e")
},
{
$push: {
naresh: {
$each: [],
$sort: {created_by: 1}
}
}
}
)