How do I group by day/month in mongoDB? - mongodb

Here's how one document looks like:
{
"login_Id": "c",
"name": "Abhishek Soni",
"location": "BLAHBLAH",
"work": [
{
"date":ISODate("2014-01-01"),
"total_time": 100,
},
{
"date":ISODate("2014-09-02"),
"total_time": 100,
},
{
"date":ISODate("2014-01-01"),
"total_time": 10,
},
]
}
What I want to do is to run a query that'll give an output like this:
{login_Id: 'c', work:{'01' : 110, '02': 100, ... and so on}}
Basically, I just want to group the work part month wise.
This is what I have tried:
db.employees.aggregate([
{
"$project": {
"_id": 0,
"login_Id": 1,
"time": {
"$sum": "$work.total_time"
}
}
},
{
"$group": {
"_id": {
"$dayOfYear": "$work.date"
},
"time": {
"$sum": "$work.total_time"
}
}
}
]);
But it outputs null. If I remove the group clause, I get the total sum (i.e., 210) What's wrong?

You can try below aggregation
db.collection.aggregate([
{ "$unwind": "$work" },
{ "$match": { "work.date": { "$type": "date" }}},
{ "$group": {
"_id": { "date": { "$dayOfMonth": "$work.date" }},
"time": { "$sum": "$work.total_time" },
"login_Id": { "$first": "$login_Id" }
}},
{ "$group": {
"_id": "$login_Id",
"data": {
"$push": {
"k": { "$toString": "$_id.date" },
"v": "$time"
}
}
}},
{ "$project": {
"work": { "$arrayToObject": "$data" },
"_id": 0,
"login_id": "$_id"
}}
])
Output
[
{
"login_id": "c",
"work": {
"1": 110,
"2": 100
}
}
]

Related

Or statement returns the first result only (should be 2)

I have an OR statement where the expected result should return 2 documents but the return result is only 1 document. I have a list of cars docs:
[
{
"_id": ObjectId("60a2c0621e5f043b735e36ef"),
"car_id": 78,
"terminal": "JFK",
"timestamp": ISODate("2020-01-01T17:00:00.000Z"),
},
{
"_id": ObjectId("60a2c0621e5f043b735e36f0"),
"car_id": 78,
"terminal": "LAX",
"timestamp": ISODate("2020-02-08T17:00:00.000Z"),
},
{
"_id": ObjectId("60a2c0621e5f043b735e36f1"),
"car_id": 78,
"terminal": "ORD",
"timestamp": ISODate("2020-03-01T17:00:00.000Z"),
},
]
and my query asks for 2 instances with SAME CAR ID but with DIFFERENT timestamp:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-15T05:00:11.000Z")
}
},
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-03-02T11:07:27.000Z")
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
"$group": {
"_id": "$car_id",
"last": {
"$last": "$$ROOT"
}
}
}
])
So I expect to get 2 results (for each request).
Expected result:
[
{
"_id": 78,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f1"),
"car_id": 78,
"terminal": "ORD",
"timestamp": ISODate("2020-03-01T17:00:00Z")
}
},
{
"_id": 78,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f0"),
"car_id": 78,
"terminal": "LAX",
"timestamp": ISODate("2020-02-08T17:00:00Z")
}
}
]
But I only get the first result. How can I get the desired 2 results?
mongoplayground
Because all of the returned docs has the same car_id and you use car_id in $group stage so they will be grouped into one result. To get your expected result, you can add a field depend on timestamp of the doc then use that filed in $group:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-15T05:00:11.000Z")
}
},
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-03-02T11:07:27.000Z")
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
$addFields: {
group: {
$cond: {
if: {
$lte: [
"$timestamp",
ISODate("2020-02-15T05:00:11.000Z")
]
},
then: 1,
else: 2
}
}
}
},
{
"$group": {
"_id": "$group",
"last": {
"$last": "$$ROOT"
}
}
}
])
Mongoplayground
Note: If you have more than one car_id in the result, you can change the $group to:
{
"$group": {
"_id": {
car_id: "$car_id",
group: "$group",
},
"last": {
"$last": "$$ROOT"
}
}
}
Mongoplayground

How to add a field to my projected result that is identical to the argument I sent

MY MONGOPLAY
How can I add a field to my result that is identical to the argument I initially sent. Note in my or statement I'm sending a date and I wish to add this date to my results (example requested_by).
I know that '$addFields' would do the trick but couldnt figure out how to integrate it.
My Query:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-09T00:00:00.000Z") //NEED THIS PARAMETER in my RESULT
}
},
{
"car_id": 79,
"timestamp": {
"$lte": ISODate("2020-03-22T00:00:00.000Z") //NEED THIS PARAMETER in my RESULT
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
"$group": {
"_id": "$car_id",
"last": {
"$last": "$$ROOT"
}
}
}
] )
expected result:
[
{
"_id": 78,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f0"),
"car_id": 78,
"terminal": "LAX",
"timestamp": ISODate("2020-02-08T17:00:00Z"),
"requested_by": ISODate("2020-02-09T00:00:00.000Z") //<--somethign like this
}
},
{
"_id": 79,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f3"),
"car_id": 79,
"terminal": "ORD",
"timestamp": ISODate("2020-03-21T17:00:00Z"),
"requested_by": ISODate("2020-03-22T00:00:00.000Z") //<--somethign like this
}
}
]
You can use '$addFields' in your aggregation as following:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-09T00:00:00.000Z")
}
},
{
"car_id": 79,
"timestamp": {
"$lte": ISODate("2020-03-22T00:00:00.000Z")
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
"$addFields": {
"requested_by": "something"
}
},
{
"$group": {
"_id": "$car_id",
"last": {
"$last": "$$ROOT"
}
}
}
])
If your 'requested_by' field should be calculated based on stored value in the object, you can use pipeline operators to calculate its value. For example:
...
{
"$addFields": {
"requested_by": "$timestamp"
}
},
...
or
...
{
"$addFields": {
"requested_by": {
"$add": [
"$car_id",
20
]
}
}
},
...

Mongodb multi level aggregation

Data in mongo
[{
"_id": "5d71d1432f7c8151c58c4481",
"payment": {
"transactions": [
{
"_id": "5d71d1ff2f7c8151c58c44cf",
"method": "paytm",
"amount": 100,
"paymentOn": "2019-09-06T03:26:44.959Z"
},
{
"_id": "5d71d1ff2f7c8151c58c44ce",
"method": "cash",
"amount": 650,
"paymentOn": "2019-09-06T03:26:55.531Z"
}
],
"status": "partial"
},
"customer": "5d66c434c24f2b1fb6772014",
"order": {
"orderNumber": "WP-ORD-06092019-001",
"total": 770,
"balance": 20
}
},
{
"_id": "5d71d1432f7c8151c58c4481",
"payment": {
"transactions": [
{
"_id": "5d71d1ff2f7c8151c58c44cf",
"method": "paytm",
"amount": 100,
"paymentOn": "2019-09-06T03:26:44.959Z"
}
],
"status": "partial"
},
"customer": "5d66c434c24f2b1fb6772014",
"order": {
"orderNumber": "WP-ORD-06092019-001",
"total": 200,
"balance": 100
}
}]
I want to aggregate payments by method.
So the result would look like below:
Output:
Paytm: 200
Cash : 650
Unpaid(Balance): 120
I have tried:
[
{
'$unwind': {
'path': '$payment.transactions',
'preserveNullAndEmptyArrays': true
}
}, {
'$project': {
'amount': '$payment.transactions.amount',
'method': '$payment.transactions.method'
}
}, {
'$group': {
'_id': '$method',
'amount': {
'$sum': '$amount'
}
}
}
]
But how to include balance calculation as well
Using the above dataset, use the aggregate pipeline for calculation using aggregate as:
db.collection.aggregate([
{
$facet: {
paidAmounts: [
{ '$unwind': { 'path': '$payment.transactions', 'preserveNullAndEmptyArrays': true } },
{
$group: {
_id: "$payment.transactions.method",
amount: {
$sum: "$payment.transactions.amount"
}
}
}
],
leftAmounts: [
{
$group: {
_id: null,
balance: {
$sum: "$order.balance"
}
}
}
]
}
}
])
giving output:
here leftAmounts has left balance and paidAmounts having grouped paid data on basis of payment type
[
{
"leftAmounts": [
{
"_id": null,
"balance": 120
}
],
"paidAmounts": [
{
"_id": "cash",
"amount": 650
},
{
"_id": "paytm",
"amount": 200
}
]
}
]
Working solution : https://mongoplayground.net/p/7IWELKKMsWe
db.collection.aggregate([
{
"$unwind": "$payment.transactions"
},
{
"$group": {
"_id": "$_id",
"balance": {
"$first": "$order.balance"
},
"paytm": {
"$sum": {
"$cond": [
{
"$eq": [
"$payment.transactions.method",
"paytm"
]
},
"$payment.transactions.amount",
0
]
}
},
"cash": {
"$sum": {
"$cond": [
{
"$eq": [
"$payment.transactions.method",
"cash"
]
},
"$payment.transactions.amount",
0
]
}
}
}
},
{
"$group": {
"_id": null,
"balance": {
"$sum": "$balance"
},
"cash": {
"$sum": "$cash"
},
"paytm": {
"$sum": "$paytm"
}
}
}
])

mongodb aggregation with multiple sub groups

I have a collection with documents that look similar to this:
[
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeA",
"color": "ColorA",
"soldFor": 12.15
},
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeA",
"color": "ColorB",
"soldFor": 13.15
},
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeB",
"color": "ColorA",
"soldFor": 12.15
},
{
"_id": ObjectId("..."),
"date": ISODate("..."),
"type": "TypeB",
"color": "ColorB",
"soldFor": 12.15
}
]
I know that this is not a good way to store such information, but unfortunately I have no influence in that.
What I need to get out of the collection is something like this:
[
2017: {
typeA: {
colorA: {
sum: 125.00
},
colorB: {
sum: 110.00
}
},
typeB: {
colorA: {
sum: 125.000
}
}
},
2016: {
typeA: {
colorB: {
sum: 125.000
}
}
}
]
At the moment I have two group stages that give me everything grouped by year, but I have no clue how to get the two other sub-groups. Building the sum would be a nice to have, but I am certain that I can figure out how that would be done in a group.
So far my pipeline looks like this:
[
{
$group: {
_id: { type: '$type', color: '$color', year: { $year: '$date' } },
docs: {
$push: '$$ROOT'
}
}
},
{
$group: {
_id: { year: '$_id.year' },
docs: {
$push: '$$ROOT'
}
}
}
]
which results in something like this:
[
{
"_id": {
"year": 2006
},
"docs": {
"_id": {
"type": "typeA",
"color": "colorA",
"year": 2006
},
"docs": [
{
... root document
}
]
}
},
{
"_id": {
"year": 2016
},
"docs": [
{
"_id": {
"type": "typeA",
"color": "colorB",
"year": 2016
},
"docs": [
{
... root document
}
]
}
... more docs with three keys in id
]
}
]
Help is much appreciated!
Using a cohort of operators found in MongoDB 3.4.4 and newer, i.e. $addFields, $arrayToObject and $replaceRoot, you can compose a pipeline like the following to get the desired result:
[
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"type": "$type",
"color": "$color"
},
"count": { "$sum": "$soldFor" }
} },
{ "$group": {
"_id": {
"year": "$_id.year",
"type": "$_id.type"
},
"counts": {
"$push": {
"k": "$_id.color",
"v": { "sum": "$count" }
}
}
} },
{ "$addFields": {
"counts": { "$arrayToObject": "$counts" }
} },
{ "$group": {
"_id": "$_id.year",
"counts": {
"$push": {
"k": "$_id.type",
"v": "$counts"
}
}
} },
{ "$addFields": {
"counts": { "$arrayToObject": "$counts" }
} },
{ "$group": {
"_id": null,
"counts": {
"$push": {
"k": { "$substr": ["$_id", 0, -1 ]},
"v": "$counts"
}
}
} },
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayToObject": "$counts" },
"$$ROOT"
]
}
} },
{ "$project": { "counts": 0 } }
]

Group and count in Mongo DB

I have many tweets object like this:
{
"_id" : ObjectId("5a2f4a381cb29b482553e2c9"),
"user_id" : 21898942,
"created_at" : ISODate("2009-03-09T19:48:50Z"),
"id" : 1301923516,
"place" : "",
"retweet_count" : 0,
"tweet" : "Save the Date! March 28th Vietnamese Cooking Class! Call to Reserve 312.255.0088",
"favorite_count" : 0
"type": A
}
I'm using this code to qroup the tweets by date and by type:
pipeline = [
{
"$group": {
"_id": {
"date": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$created_at"
}
},
"type": "$type"
},
"count": {
"$sum": 1
}
}
}
]
results = mongo.db.tweets.aggregate(pipeline)
Here is the result I get:
{
"_id": {
"date": "2009-03-17",
"type": A
},
"count": 4
,
{
"_id": {
"date": "2009-03-17",
"type": B
},
"count": 6
}
But now I want to have the result in this format:
{date: "2009-03-17", A: 4, B: 6, C: 9}
Is there anyway I can achieve this through aggregate directly?
Note: I'm using MongoDB and PyMongo
You can try the below aggregation query in 3.6 version.
Added the second group to create array of type and count value pairs followed by $mergeObjects to merge date key value with $arrayToObject, which produces create a type value key and count value pairs, to generate the expected response.
$replaceRoot to promote the document to the top level.
pipeline = [
{
"$group": {
"_id": {
"date": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$created_at"
}
},
"type": "$type"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.date",
"typeandcount": {
"$push": {
"k": "$_id.type",
"v": "$count"
}
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{
"date": "$_id"
},
{
"$arrayToObject": "$typeandcount"
}
]
}
}
}
]
Mongo 3.4 version:
Replace the last stage with below
{
"$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[
{
"k": "date",
"v": "$_id"
}
],
"$typeandcount"
]
}
}
}
}