Create new properties based on embedded array elements in MongoDB - mongodb

I have this kind of document
{
"_id" : ObjectId("573342930348ce88ff1685f3"),
"presences" : [
{
"_id" : ObjectId("573342930348ce88ff1685f2"),
"createdAt" : NumberLong(1458751869000)
},
{
"_id" : ObjectId("573342930348ce88ff1685f5"),
"createdAt" : NumberLong(1458751885000)
},
{
"_id" : ObjectId("573342930348ce88ff1685f7"),
"createdAt" : NumberLong(1458751894000)
}
]
}
How can I extract first and last presences element to new properties firstPresence and lastPresence like this?
{
"_id" : ObjectId("573342930348ce88ff1685f3"),
"firstPresence": {
"_id" : ObjectId("573342930348ce88ff1685f2"),
"createdAt" : NumberLong(1458751869000)
},
"lastPresence": {
"_id" : ObjectId("573342930348ce88ff1685f7"),
"createdAt" : NumberLong(1458751894000)
},
"presences" : [
...
]
}
I want to use a query that can be applied to all documents in one time.

You need to $unwind your presences array to do the aggregation. Before grouping you can sort them by createdAt to utilize $first and $last operators.
db.collection.aggregate(
[
{ $unwind: "$presences" },
{ $sort: { "presences.createdAt": 1 } },
{
$group: {
_id: "$_id",
"presences": { $push: "$presences" },
"lastPresence": { $last: "$presences" },
"firstPresence": { $first: "$presences" },
}
},
{ $out : "collection" }
])
Last aggregation pipeline ($out) will replace existing collection.

According to above mentioned description as a solution to it please try executing following aggregate query into MongoDB shell
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$project: {
first: {
$arrayElemAt: ["$presences", 0]
},
last: {
$arrayElemAt: ["$presences", -1]
},
presences: 1
}
},
]
);

Related

I have an array of objects and i want to get the latest createdAt and then apply a filter of date range if it falls in that date and then count it

{
"_id" : ObjectId("61a765e6f664eb8f6b12c"),
"details" : [
{
"_id" : ObjectId("60c84d9968c2d100154f3391"),
"expiryDate" : ISODate("2021-06-12T05:30:00.000Z"),
"updatedAt" : ISODate("2021-06-15T06:50:01.046Z"),
"createdAt" : ISODate("2021-06-10T06:50:01.046Z")
},
{
"_id" : ObjectId("60c84d99c2d100154f3391"),
"expiryDate" : ISODate("2021-06-25T05:30:00.000Z"),
"updatedAt" : ISODate("2021-06-15T06:50:01.046Z"),
"createdAt" : ISODate("2021-06-16T06:50:01.046Z")
},
{
"_id" : ObjectId("60c84d9968c20154f3391"),
"expiryDate" : ISODate("2021-06-25T05:30:00.000Z"),
"updatedAt" : ISODate("2021-06-15T06:50:01.046Z"),
"createdAt" : ISODate("2021-06-15T06:50:01.046Z")
}
]
}
How can i write mongo query to sort and get the latest date and then apply date range filter on that
You can $addFields an auxilary field lastCreatedAt by using $max. Then $match on the field in an aggregation pipeline.
db.collection.aggregate([
{
"$addFields": {
"lastCreatedAt": {
$max: "$details.createdAt"
}
}
},
{
"$match": {
lastCreatedAt: {
// input your date range here
$gte: ISODate("2012-06-16T06:50:01.000Z"),
$lt: ISODate("2021-12-30T06:50:01.100Z")
}
}
},
{
$group: {
_id: null,
docs: {
$push: "$$ROOT"
},
numOfLastCreatedAt: {
$sum: 1
}
}
}
])
Here is the Mongo playground for your reference.

I need limited nested array in mongodb document

I have a document like
{
"deviceId" : "1106",
"orgId" : "5ffe9fe1c9e77c0006f0aad3",
"values" : [
{
"paramVal" : 105.0,
"dateTime" : ISODate("2021-05-05T09:18:08.000Z")
},
{
"paramVal" : 110.0,
"dateTime" : ISODate("2021-05-05T09:18:08.000Z")
},
{
"paramVal" : 115.0,
"dateTime" : ISODate("2021-05-05T10:18:08.000Z")
},
{
"paramVal" : 125.0,
"dateTime" : ISODate("2021-05-05T11:18:08.000Z")
},
{
"paramVal" : 135.0,
"dateTime" : ISODate("2021-05-05T12:18:08.000Z")
}
]
}
Now I need to filter a document which I can do easily with match or find but in that document the subarray i.e. values should have latest 2 values because in future the count can be more than 100.
the output should be like
{
"deviceId" : "1106",
"orgId" : "5ffe9fe1c9e77c0006f0aad3",
"values" : [
{
"paramVal" : 125.0,
"dateTime" : ISODate("2021-05-05T11:18:08.000Z")
},
{
"paramVal" : 135.0,
"dateTime" : ISODate("2021-05-05T12:18:08.000Z")
}
]
}
Try $slice operator, to select number of elements, pass negative value to select documents from below/last elements,
db.collection.aggregate([
{ $set: { values: { $slice: ["$values", -2] } } }
])
Playground
I need for the array values in sorted order by date
There is no straight way to do this, check the below aggregation query, but it will cause the performance issues, i would suggest to change you schema structure to manage this data order by date,
$unwind deconstruct values array
$sort by dateTime in descending order
$group by _id and reconstruct values array and return other required fields
$slice to select number of elements, pass negative value to select documents from below/last elements
db.collection.aggregate([
{ $unwind: "$values" },
{ $sort: { "values.dateTime": -1 } },
{
$group: {
_id: "$_id",
deviceId: { $first: "$deviceId" },
orgId: { $first: "$orgId" },
values: { $push: "$values" }
}
},
{ $set: { values: { $slice: ["$values", 2] } } }
])
Playground

How to convert multiple documents from a single collection to a single document containing one array

I have an aggregation pipeline that nearly does what I want. I've used match / unwind / project / sort to get 99% of the way. It is returning multiple documents:
[
{
"_id" : 254.8
},
{
"_id" : 93.7
},
{
"_id" : 89.9
},
{
"_id" : 94.15
},
{
"_id" : 102.1
},
{
"_id" : 93.9
},
{
"_id" : 102.7
}
]
Note: I've added the array brackets and commas to make it more readable, but you can also read it as:
{
"_id" : 254.8
}
{
"_id" : 93.7
}
{
"_id" : 89.9
}
{
"_id" : 94.15
}
{
"_id" : 102.1
}
I need the contents of the ID fields from all 7 documents in an array of values in one document:
{values: [254.8, 93.7, 89.9, 94.15, 102.1, 93.9, 102.7]}
It would be easy to sort this with JS once I have the results but I'd rather do it in the pipeline if possible so my JS stays 100% generic and only returns pure pipeline data.
Here is what you need to complete the job:
db.collection.aggregate([
{
"$group": {
"_id": null,
"values": {
$push: "$_id"
}
}
},
{
"$project": {
_id: false
}
}
])
The result will be:
[
{
"values": [
254.8,
93.7,
89.9,
94.15,
102.1,
93.9,
102.7
]
}
]
https://mongoplayground.net/p/pTmR_rni0J1

Multiple grouping in mongodb

Sample Colloection Data :
{
"_id" : ObjectId("5f30df23243ffsdfwer3d14568bf"),
"value" : {
"busId" : 200.0,
"status" : {
"code" : {
"id" : 1.0,
"key" : "2100",
"value" : "Complete"
}
}
}
}
My Query does provides the right result, but would like to squeeze the output more by using multiple grouping or $project or any other aggregators.
mongo Query:
db.suraj_coll.aggregate([
{
$addFields: {
"value.available": {
$cond: [
{
$in: [
"$value.status.code.value",
[
"Accept",
"Complete"
]
]
},
"Approved",
"Rejected"
]
}
}
},
{
"$group": {
"_id": {
busID: "$value.busId",
status: "$value.available"
},
"subtotal": {
$sum: 1
}
}
}
])
Output:
/* 1 */
{
"_id" : {
"busID" : 200.0,
"status" : "Approved"
},
"subtotal" : 3.0
}
/* 2 */
{
"_id" : {
"busID" : 200.0,
"status" : "Rejected"
},
"subtotal" : 1.0
}
Is it possible to squeeze the output more by using any further grouping ?
Output should look like below
{
"_id" : {
"busID" : 200.0,
"Approved" : 3.0
"Rejected" : 1.0
}
}
tried with $project, by keeping the count in a doc , but couldn't place the count against Approve or Rejected.
Any suggestion would be great.
You can use more two pipelines after your query,
$group by busID and push status and count in status
$project to convert status array to object using $arrayToObject and merge with busID using $mergeObjects
{
$group: {
_id: "$_id.busID",
status: {
$push: {
k: "$_id.status",
v: "$subtotal"
}
}
}
},
{
$project: {
_id: {
$mergeObjects: [
{ busID: "$_id" },
{ $arrayToObject: "$status" }
]
}
}
}
Playground

mongodb count number of documents for every category

My collection looks like this:
{
"_id":ObjectId("5744b6cd9c408cea15964d18"),
"uuid":"bbde4bba-062b-4024-9bb0-8b12656afa7e",
"version":1,
"categories":["sport"]
},
{
"_id":ObjectId("5745d2bab047379469e10e27"),
"uuid":"bbde4bba-062b-4024-9bb0-8b12656afa7e",
"version":2,
"categories":["sport", "shopping"]
},
{
"_id":ObjectId("5744b6359c408cea15964d15"),
"uuid":"561c3705-ba6d-432b-98fb-254483fcbefa",
"version":1,
"categories":["politics"]
}
I want to count the number of documents for every category. To do this, I unwind the categories array:
db.collection.aggregate(
{$unwind: '$categories'},
{$group: {_id: '$categories', count: {$sum: 1}} }
)
Result:
{ "_id" : "sport", "count" : 2 }
{ "_id" : "shopping", "count" : 1 }
{ "_id" : "politics", "count" : 1 }
Now I want to count the number of documents for every category, but where document version is the latest version.
This is where I am stuck.
It's ugly but I think this gives you what you're after:
db.collection.aggregate(
{ $unwind : "$categories" },
{ $group :
{ "_id" : { "uuid" : "$uuid" },
"doc" : { $push : { "version" : "$version", "category" : "$categories" } },
"maxVersion" : { $max : "$version" }
}
},
{ $unwind : "$doc" },
{ $project : { "_id" : 0, "uuid" : "$id.uuid", "category" : "$doc.category", "isCurrentVersion" : { $eq : [ "$doc.version", "$maxVersion" ] } } },
{ $match : { "isCurrentVersion" : true }},
{ $group : { "_id" : "$category", "count" : { $sum : 1 } } }
)
You can do this by first grouping the denormalized documents (from the $unwind operator step) by two keys, i.e. the categories and version fields. This is necessary for the preceding pipeline step which orders the grouped documents and their accumulated counts by the version (desc) and categories (asc) keys respectively using the $sort operator.
Another grouping will be required to get the top documents in each categories group after ordering using the $first operator. The following shows this
db.collection.aggregate(
{ "$unwind": "$categories" },
{
"$group": {
"_id": {
'categories': '$categories',
'version': '$version'
},
"count": { "$sum": 1 }
}
},
{ "$sort": { "_id.version": -1, "_id.categories": 1 } },
{
"$group": {
"_id": "$_id.categories",
"count": { "$first": "$count" },
"version": { "$first": "$_id.version" }
}
}
)
Sample Output
{ "_id" : "shopping", "count" : 1, "version" : 2 }
{ "_id" : "sport", "count" : 1, "version" : 2 }
{ "_id" : "politics", "count" : 1, "version" : 1 }