I'm trying to filter through an array of objects in a user collection on MongoDB. The structure of this particular collection looks like this:
name: "John Doe"
email: "john#doe.com"
progress: [
{
_id : ObjectId("610be25ae20ce4872b814b24")
challenge: ObjectId("60f9629edd16a8943d2cab9b")
date_unlocked: 2021-08-05T12:15:32.129+00:00
completed: true
date_completed: 2021-08-06T12:15:32.129+00:00
}
{
_id : ObjectId("611be24ae32ce4772b814b32")
challenge: ObjectId("60g6723efd44a6941l2cab81")
date_unlocked: 2021-08-06T12:15:32.129+00:00
completed: true
date_completed: 2021-08-07T12:15:32.129+00:00
}
]
date: 2021-08-04T13:06:34.129+00:00
How can I query the database using mongoose to return only the challenge with the most recent 'date_unlocked'?
I have tried: User.findById(req.user.id).select('progress.challenge progress.date_unlocked').sort({'progress.date_unlocked': -1}).limit(1);
but instead of returning a single challenge with the most recent 'date_unlocked', it is returning the whole user progress array.
Any help would be much appreciated, thank you in advance!
You can try this.
db.collection.aggregate([
{
"$unwind": {
"path": "$progress"
}
},
{
"$sort": {
"progress.date_unlocked": -1
}
},
{
"$limit": 1
},
{
"$project": {
"_id": 0,
"latestChallenge": "$progress.challenge"
}
}
])
Test the code here
Alternative solution is to use $reduce in that array.
db.collection.aggregate([
{
"$addFields": {
"latestChallenge": {
"$arrayElemAt": [
{
"$reduce": {
"input": "$progress",
"initialValue": [
"0",
""
],
"in": {
"$let": {
"vars": {
"info": "$$value",
"progress": "$$this"
},
"in": {
"$cond": [
{
"$gt": [
"$$progress.date_unlocked",
{
"$arrayElemAt": [
"$$info",
0
]
}
]
},
[
{
"$arrayElemAt": [
"$$info",
0
]
},
"$$progress.challenge"
],
"$$info"
]
}
}
}
}
},
1
]
}
}
},
{
"$project": {
"_id": 0,
"latestChallenge": 1
}
},
])
Test the code here
Mongoose can use raw MQL so you can use it.
Related
So in mongodb 3.2 (for reasons, we can't upgrade yet) I have a bunch of documents in this structure:
// Document 1
{
"id": "record-1",
"childItems": [
"child-1",
"child-2"
],
"specLookup":[
{
"specId": "spec-1"
},
{
"specId": "spec-2"
}
]
},
// Document 2
{
"id": "record-2",
"childItems": [
"child-3"
],
"specLookup":[
{
"specId": "spec-3"
}
]
}
I need an aggregation query that will merge and manipulate all these records into one document, while maintaining all individual ids in a new array. So based on the two documents above I'd end up with this:
{
"ids": ["record-1", "record-2"],
"childItems": [
"child-1",
"child-2",
"child-3"
],
"specIds":[
"spec-1"
"spec-2",
"spec-3"
]
}
How can I do this? Cheers!
You can do it like this:
$group - to group all the documents add collect all the ids, childItems and specLookup.
$reduce with $concatArrays - to get the data in the format you want.
db.collection.aggregate([
{
"$group": {
"_id": null,
"ids": {
"$addToSet": "$id"
},
"childItems": {
"$addToSet": "$childItems"
},
"specLookup": {
"$addToSet": "$specLookup.specId"
}
}
},
{
"$set": {
"childItems": {
"$reduce": {
"input": "$childItems",
"initialValue": [],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
},
"specLookup": {
"$reduce": {
"input": "$specLookup",
"initialValue": [],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
}
}
}
])
Working example
I have the following mongodb structure...
[
{
track: 'Newcastle',
time: '17:30',
date: '22/04/2022',
bookmakers: [
{
bookmaker: 'Coral',
runners: [
{
runner: 'John',
running: true,
odds: 3.2
},
...
]
},
...
]
},
...
]
I'm trying to find filter the bookmakers array for each document to only include the objects that match the specified bookmaker values, for example:
{ 'bookmakers.bookmaker': { $in: ['Coral', 'Bet365'] } }
At the moment, I'm using the following mongodb query to only select the bookmakers that are specified, however I need to put the documents back together after they've been seperated by the '$unwind', is there a way I can do this using $group?
await HorseRacingOdds.aggregate([
{ $unwind: "$bookmakers" },
{
$group: {
_id: "$_id",
bookmakers: "$bookmakers"
}
},
{
$project: {
"_id": 0,
"__v": 0,
"lastUpdate": 0
}
}
])
How about a plain $addFields with $filter?
db.collection.aggregate([
{
"$addFields": {
"bookmakers": {
"$filter": {
"input": "$bookmakers",
"as": "b",
"cond": {
"$in": [
"$$b.bookmaker",
[
"Coral",
"Bet365"
]
]
}
}
}
}
},
{
$project: {
"_id": 0,
"__v": 0,
"lastUpdate": 0
}
}
])
Here is the Mongo playground for your reference.
I'm new in mongoDB.
This is one example of record from collection:
{
supplier: 1,
type: "sale",
items: [
{
"_id": ObjectId("60ee82dd2131c5032342070f"),
"itemBuySum": 10
},
{
"_id": ObjectId("60ee82dd2131c50323420710"),
"itemBuySum": 10,
},
{
"_id": ObjectId("60ee82dd2131c50323420713"),
"itemBuySum": 10
},
{
"_id": ObjectId("60ee82dd2131c50323420714"),
"itemBuySum": 20
}
]
}
I need to group by TYPE field and get the SUM. This is output I need:
{
supplier: 1,
sales: 90,
returns: 170
}
please check Mongo playground for better understand. Thank you!
$match - Filter documents.
$group - Group by type and add item into data array which leads to the result like:
[
[/* data 1 */],
[/* data 2 */]
]
$project - Decorate output document.
3.1. First $reduce is used to flatten the nested array to a single array (from Result (2)) via $concatArrays.
3.2. Second $reduce is used to aggregate $sum the itemBuySum.
db.collection.aggregate({
$match: {
supplier: 1
},
},
{
"$group": {
"_id": "$type",
"supplier": {
$first: "$supplier"
},
"data": {
"$push": "$items"
}
}
},
{
"$project": {
_id: 0,
"supplier": "$supplier",
"type": "$_id",
"returns": {
"$reduce": {
"input": {
"$reduce": {
input: "$data",
initialValue: [],
in: {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
},
"initialValue": 0,
"in": {
$sum: [
"$$value",
"$$this.itemBuySum"
]
}
}
}
}
})
Sample Mongo Playground
db.collection.aggregate([
{
$match: {
supplier: 1
},
},
{
"$group": {
"_id": "$ID",
"supplier": {
"$first": "$supplier"
},
"sale": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$type",
"sale"
]
},
"then": {
"$sum": "$items.itemBuySum"
},
"else": {
"$sum": 0
}
}
}
},
"returns": {
"$sum": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$type",
"return"
]
},
"then": {
"$sum": "$items.itemBuySum"
},
"else": {
"$sum": 0
}
}
}
}
}
}
},
{
"$project": {
_id: 0,
supplier: 1,
sale: 1,
returns: 1
}
}
])
I have next simplified collection
[
{
"key": 1,
"array": [
{ "check": true },
{ "check": false },
{ "check": true }
]
},
{
"key": 2
}
]
I want to add field "count" with number of elements of array with "check"=true, so I expect next result
{
"key": 1,
"array": [
{ "check": true },
{ "check": false },
{ "check": true }
],
"count":2,
},
{
"key": 2,
"count": 0
}
]
I have next query ( it is aggregation, because actually it one of stages of pipeline)
db.collection.aggregate([
{
"$addFields": {
"count": {
"$sum": {
"$cond": {
"if": {
"$eq": ["$array.check",true],
},
"then": 1,
"else": 0,
}
}
},
}
}
])
But I always get count=0.
Can you help me to find error in my query?
Here mongo playground
Instead of using $sum, you can use $filter to filter only the array with "check"=true, then check the size of the resulting array using $size.
db.collection.aggregate([
{
"$addFields": {
"count": {
"$size": {
"$filter": {
"input": { "$ifNull": ["$array", []] }, // default empty array if array is does not exist
"cond": "$$this.check" // only keep the truthy check value
}
}
}
}
}
])
Mongo Playground
Alternatively, if you want to use $sum, you could also map the array to an array of 0 and 1 according to the check value, using $map
db.collection.aggregate([
{
"$addFields": {
"count": {
"$sum": {
"$map": {
"input": { "$ifNull": ["$array", []] },
"in": {
"$cond": ["$$this.check", 1, 0]
}
}
}
}
}
}
])
Mongo Playground
Here is how to achieve this using $reduce
db.collection.aggregate([
{
"$addFields": {
"count": {
"$sum": {
$reduce: {
input: "$array",
initialValue: 0,
in: {
$sum: [
"$$value",
{
$cond: [
"$$this.check",
1,
0
]
}
]
}
}
}
}
}
}
])
Mongo Playground
Using $reduce aggregation array expression operator,
db.test.aggregate( [
{
$addFields: {
count: {
$reduce: {
input: { $ifNull: [ "$array", [] ] },
initialValue: 0,
in: {
$cond: [ { $eq: [ "$$this.check", true ] },
{ $add: [ "$$value", 1 ] },
"$$value"
]
}
}
}
}
}
] )
gets a result as follows:
{
"_id" : ObjectId("5f0c7b691c7c98bb49fd2b50"),
"key" : 1,
"array" : [
{
"check" : true
},
{
"check" : false
},
{
"check" : true
}
],
"count" : 2
}
{ "_id" : ObjectId("5f0c7b691c7c98bb49fd2b51"), "key" : 2, "count" : 0 }
hello all i'm working with a MongoDB database where each data row is like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
"userhasRate" : [
"51",
"52",
],
"ratings" : [
4,
3
],
}
and i need to change it to be like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
rate : [
{userhasrate: "51", value: 4},
{userhasrate: "52", value: 3},
]
}
I already try using this method,
db.getCollection('contens').aggregate([
{ '$group':{
'rates': {$push:{ value: '$ratings', user: '$userhasRate'}}
}
}
]);
and my result become like this
{
"rates" : [
{
"value" : [
5,
5,
5
],
"user" : [
"51",
"52",
"53"
]
}
]
}
Can someone help me to solve my problem,
Thank you
You can use $arrayToObject and $objectToArray inside $map to achieve the required output.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
}
}
},
"as": "el",
"in": {
"userhasRate": "$$el.k",
"value": "$$el.v"
}
}
}
}
}
])
Alternative Method
If userhasRate contains repeated values then the first solution will not work. You can use arrayElemAt and $map along with $zip if it contains repeated values.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
},
"as": "el",
"in": {
"userhasRate": {
"$arrayElemAt": [
"$$el",
0
]
},
"value": {
"$arrayElemAt": [
"$$el",
1
]
}
}
}
}
}
}
])
Try below aggregate, first of all you used group without _id that grouped all the JSONs in the collection instead set it to "$_id" also you need to create 2 arrays using old data then in next project pipeline concat the arrays to get desired output:
db.getCollection('contens').aggregate([
{
$group: {
_id: "$_id",
rate1: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
0
]
},
value: {
$arrayElemAt: [
"$ratings",
0
]
}
}
},
rate2: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
1
]
},
value: {
$arrayElemAt: [
"$ratings",
1
]
}
}
}
}
},
{
$project: {
_id: 1,
rate: {
$concatArrays: [
"$rate1",
"$rate2"
]
}
}
}
])