MongoDB find only the newest combination of fields - mongodb

I have 3 collections:
1:
{
"_id": {
"$oid": "5f37dad305c1b9403bfe808b"
},
"nextBilling": {
"isAutomatic": true,
"isRedeemedForFutureBilling": false,
"time": 1000,
"duration": 1000
},
"userToken": "aaaa",
"__v": 0
}
2:
{
"_id": {
"$oid": "5f37dad305c1b9403bfe323b"
},
"nextBilling": {
"isAutomatic": true,
"isRedeemedForFutureBilling": false,
"time": 2000,
"duration": 1000
},
"userToken": "aaaa",
"__v": 0
}
3:
{
"_id": {
"$oid": "5f37dfj1b9403bfe323b"
},
"nextBilling": {
"isAutomatic": true,
"isRedeemedForFutureBilling": false,
"time": 1000,
"duration": 1000
},
"userToken": "bbbb",
"__v": 0
}
These are payments from users, and I want to get every users' payments, but only and only their last payment (which can be determined by nextBilling.time). So what I want to get at the very last is collections number 2 and 3. Users are identified by the userToken, so what I actually want is all combinations of (userToken and nextBilling.time) where nextBilling.time is the largest and only one result should be returned for each userToken.
What i'd do in mySql for achieving this would be sth like:
SELECT * FROM payments WHERE (user_token, next_billing_time) IN (SELECT user_token, MAX(ts_in_sec) FROM payments group by user_token)
I'm a bit lost about what I should do in mongoDB for this. I'm using mongoose.

You can try using aggregate(),
$sort by nextBilling.time descending order
db.collection.aggregate([
{ $sort: { "nextBilling.time": -1 } },
$group by userToken and set first nextBilling and id
{
$group: {
_id: "$userToken",
id: { $first: "$_id" },
nextBilling: { $first: "$nextBilling" }
}
},
$project to show required fields
{
$project: {
_id: "$id",
userToken: "$_id",
nextBilling: 1
}
}
])
Playground

You can do :
const result = await ModelName.aggregate([
{$group: {_id: '$userToken', "time": {$max: "$nextBilling.time"}}}
])

Related

Calculate distinct count on fields in mongodb

I have following collection:
[{
"id": 1,
"activity_type": "view",
"user_id": 1
},
{
"id": 2,
"activity_type": "save",
"user_id": 1
},
{
"id": 3,
"activity_type": "save",
"user_id": 1
},
{
"id": 4,
"activity_type": "save",
"user_id": 2
}]
I need to get a result like this:
[{
"activity_type": "view",
"count": 1,
"user_count": 1
},{
"activity_type": "save",
"count": 3,
"user_count": 2
}]
So far I reached on this:
db.getCollection('activities').aggregate([
{
$group:{_id:"$activity_type", count: {$sum: 1}}
},
{
$project:
{
_id: 0,
activity_type: "$_id",
count: 1
}
}
])
It gives me:
[{
"activity_type": "view",
"count": 1
},
{
"activity_type": "save",
"count": 3
}]
How can I add distinct user_id count as well?
What you need to do is use $addToSet in the group stage to gather the unique ids, after that in the $project stage you can use $size to show the proper user count.
db.collection.aggregate([
{
$group: {
_id: "$activity_type",
count: {
$sum: 1
},
user_ids: {
"$addToSet": "$user_id"
}
}
},
{
$project: {
_id: 0,
activity_type: "$_id",
user_count: {
$size: "$user_ids"
},
count: 1
}
}
])
Mongo Playground

mongodb query to find the min price in array of objects

My documents:
[{
"title": "lenovo x-100",
"brand": "lenovo",
"category": "laptops",
"variant": [{
"price": 30000,
"RAM": "4GB",
"storage": "256GB",
"screen": "full hd",
"chip": "i3"
}, {
"price": 35000,
"RAM": "8GB",
"storage": "512GB",
"screen": "full hd",
"chip": "i5"
}, {
"price": 40000,
"RAM": "12GB",
"storage": "2TB",
"screen": "uhd",
"chip": "i7"
}],
"salesCount": 32,
"buysCount": 35,
"viewsCount": 60
},
{
"title": "samsung12",
"brand": "lenovo",
"category": "mobile phones",
"variant": [{
"price": 11000,
"RAM": "4GB",
"ROM": "32GB"
}, {
"price": 16000,
"RAM": "6GB",
"ROM": "64GB"
}, {
"price": 21000,
"RAM": "8GB",
"ROM": "128GB"
}],
"salesCount": 48,
"buysCount": 39,
"viewsCount": 74
}
Expected output
{
_id:"lenovo",
minPrice:1100
}
I have tried this method of aggregation
[{
$match: {
brand: 'lenovo'
}
}, {
$group: {
_id: '$brand',
prices: {
$min: '$variant.price'
}
}
}, {
$unwind: {
path: '$prices'
}
}, {
$group: {
_id: '$_id',
minPrice: {
$min: '$prices'
}
}
}]
I want to find the minimum price based on the brand, this query is returning the expected output but is there any better way to get the expected outcome because using $unwind operator in quite expensive in the sense it may take longer execution time, hoping for positive response.Thanks in advance.
You can use $reduce to replace the second $group stage.
$match
$group - Push variant.price into new array and results nested array of array.
$project:
3.1. $reduce - Use to flatten the nested array from the result 2 by $concat the arrays into one.
3.2. $min - Select min value from the result 3.1.
db.collection.aggregate([
{
$match: {
brand: "lenovo"
}
},
{
$group: {
_id: "$brand",
prices: {
$push: "$variant.price"
}
}
},
{
$project: {
_id: 1,
minPrice: {
$min: {
"$reduce": {
"input": "$prices",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
}
}
}
}
])
Sample Mongo Playground

Find documents that share one key but differ in another

I have a mongodb collection that is resembles
{"dept":"A" , "email":"bob#example.com", "userID": "1"}
{"dept":"A" , "email":"bob#example.com", "userID": "1"}
{"dept":"A" , "email":"bob#example.com", "userID": "2"} <<< "bad" record
{"dept":"A" , "email":"alice#example.com", "userID": "3"}
{"dept":"B" , "email":"bob#example.com", "userID": "4"}
{"dept":"B" , "email":"kevin#example.com", "userID": "5"}
The constraint is that an email must only have a single userID per department.
How would I query the table to find which emails have multiple userIDs within a department? Mongo 4.4+
You have to use two $group pipeline stages to filter and find records with multiple entries.
db.collection.aggregate([
{
"$group": {
"_id": {
"dept": "$dept",
"email": "$email",
"userID": "$userID",
},
"individualCount": {
"$sum": 1
}
},
},
{
"$group": {
"_id": "$_id.email",
"userIDs": {
"$addToSet": "$_id.userID"
},
"dept": {
"$addToSet": "$_id.dept"
},
"totalRecordsCount": {
"$sum": "$individualCount"
},
"totalDuplicCounts": {
"$sum": 1
},
},
},
{
"$match": {
"totalDuplicCounts": {
"$gt": 1
}
},
},
])
Mongo Playground Sample Execution

Grouping by array elements and find sum of common array fields

The document has an array field and each of them has a unique Id field. After doing unwind on the array, I need to add a count field on each array document instead of Id. The count should be the sum of similar array fields (grouped by fields other than Id).
Sample document - https://mongoplayground.net/p/LUPqVw07unP
Expected result:
{
"originId": 123,
"taskMetric": {
"count": 2,
"status": "OPEN",
"assignee": "ABC"
}
}, {
"originId": 1,
"taskMetric": {
"count": 1,
"status": "COMPLETED",
"assignee": "CDE"
}
}, {
"originId": 1,
"taskMetric": {
"count": 1,
"status": "COMPLETED",
"assignee": "EFG"
}
}
Play
You have to add one more projection stage to avoid _id and add id to group stage as other fields to the following query.
db.collection.aggregate([
{
$unwind: "$tasks"
},
{
$group: {
"_id": {
"status": "$tasks.status",
"assignee": "$tasks.assignee"
},
"count": {
$sum: 1
},
"status": {
$first: "$tasks.status"
},
"assignee": {
$first: "$tasks.assignee"
}
}
}
])

How to $push a field depending on a condition?

I'm trying to conditionally push a field into an array during the $group stage of the MongoDB aggregation pipeline.
Essentially I have documents with the name of the user, and an array of the actions they performed.
If I group the user actions like this:
{ $group: { _id: { "name": "$user.name" }, "actions": { $push: $action"} } }
I get the following:
[{
"_id": {
"name": "Bob"
},
"actions": ["add", "wait", "subtract"]
}, {
"_id": {
"name": "Susan"
},
"actions": ["add"]
}, {
"_id": {
"name": "Susan"
},
"actions": ["add, subtract"]
}]
So far so good. The idea would be to now group together the actions array to see which set of user actions are the most popular. The problem is that I need to remove the "wait" action before taking into account the group. Therefore the result should be something like this, taking into account that the "wait" element should not be considered in the grouping:
[{
"_id": ["add"],
"total": 1
}, {
"_id": ["add", "subtract"],
"total": 2
}]
Test #1
If I add this $group stage:
{ $group : { _id : "$actions", total: { $sum: 1} }}
I get the count that I want, but it takes into account the unwanted "wait" array element.
[{
"_id": ["add"],
"total": 1
}, {
"_id": ["add", "subtract"],
"total": 1
}, {
"_id": ["add", "wait", "subtract"],
"total": 1
}]
Test #2
{ $group: { _id: { "name": "$user.name" }, "actions": { $push: { $cond: { if:
{ $ne: [ "$action", 'wait']}, then: "$action", else: null } }}} }
{ $group : { _id : "$actions", total: { $sum: 1} }}
This is as close as I've gotten, but this pushes null values where the wait would be, and I can't figure out how to remove them.
[{
"_id": ["add"],
"total": 1
}, {
"_id": ["add", "subtract"],
"total": 1
}, {
"_id": ["add", null, "subtract"],
"total": 1
}]
UPDATE:
My simplified documents look like this:
{
"_id": ObjectID("573e0c6155e2a8f9362fb8ff"),
"user": {
"name": "Bob",
},
"action": "add",
}
You need a preliminary $match stage in your pipeline to select only those documents where "action" is not equals to "wait".
db.collection.aggregate([
{ "$match": { "action": { "$ne": "wait" } } },
{ "$group": {
"_id": "$user.name",
"actions": { "$push": "$action" },
"total": { "$sum": 1 }
}}
])