update so Mohammad Faisal has the best solution.However it breaks when a new document is added lol! so i learned a lot from his code and modified it and it Works! =) the code is all the way in the bottom.
But here's what i said..
So i have this document
{"_id":"5ddea2e44eb407059828d740",
"projectname":"wdym",
"username":"easy",
"likes":0,
"link":["ssss"]
}
{"_id":"5ddea2e44eb407059822d740",
"projectname":"thechosenone",
"username":"easy",
"likes":30,
"link":["ssss"]
}
{"_id":"5ddea2e44eb407059828d740",
"projectname":"thanos",
"username":"wiley",
"likes":10,
"link":["ssss"]
}
and basically what i want is the document that contains the highest
likes with it's associated project name
For example the output would be
"projectname":"thechosenone",
"username":"easy",
"likes":30
}
,
{
"projectname":"thanos",
"username":"wiley",
"likes":10,
}
the code i have for this is the following
db
.collection("projects")
.aggregate([
{
$group: {
_id: { username: "$username" },
likes: { $max: "$likes" }
}
},
{
$project:{projectname:1}
}
])
$project gives me a strange output. However,
the output was correct without the $project.
But i wanted to project the projectname, the user and the highest likes. Thanks for hearing me out :)
heres the solution =)
db
.collection("projects")
.aggregate([
{
$sort: {
likes: -1
}
},
{
$group: {
_id: {
username: "$username"
},
likes: {
$max: "$likes"
},
projectname: {
$push: "$projectname"
},
link: {
$push: "$link"
}
}
},
{
$project: {
username: "$_id.username",
projectname: {
$arrayElemAt: ["$projectname", 0]
},
link: {
$arrayElemAt: ["$link", 0]
}
}
}
])
.toArray()
If you don't have to use $group this will solve your problem:
db.projects.aggregate([
{$sort:{likes:-1}},
{$limit:1}
]).pretty()
the result would be
{
"_id" : ObjectId("5ddee7f63cee7cdf247059db"),
"projectname" : "thechosenone",
"username" : "easy",
"likes" : 30,
"links" : ["ssss"]
}
Try this:-
db.collection("projects").aggregate([
{
$group: {
_id: { username: "$username" },
likes: { $max: "$likes" },
projectname: { $push : { $cond: [ { $max: "$likes" }, "$projectname", "" ]}}
}
}
,
{
$project:{
username:"$_id.username",
projectname:{"$reduce": {
"input": "$projectname",
"initialValue": { "$arrayElemAt": ["$projectname", 0] },
"in": { "$cond": [{ "$ne": ["$$this", ""] }, "$$this", "$$value"] }
}},
likes:1
}
}
])
Related
hoping someone can help as I am truly stuck!
I have this query
SwapModel.aggregate([
{
$match: {
organisationId: mongoose.Types.ObjectId(organisationId),
matchId: null,
matchStatus: 0,
offers: {
$elemMatch: {
from: { $lte: new Date(from) },
to: { $gte: new Date(to) },
locations: { $elemMatch: { $eq: location } },
types: { $elemMatch: { $eq: type } },
},
},
//problem is HERE
$or: {
$map: {
input: "$offers",
as: "offer",
in: {
from: { $gte: new Date("$$offer.from") },
to: { $lte: new Date("$$offer.to") },
location: { $in: "$$offer.locations" },
type: { $in: "$$offer.types" },
},
},
},
},
},
{ ...swapUserLookup },
{ $unwind: "$matchedUser" },
{ $sort: { from: 1, to: 1 } },
]);
I'm trying to use the results of the $match document to generate an array for $or. My data looks like this:
[{
_id: ObjectId("id1"),
from: ISODate("2023-01-21T06:30:00.000Z"),
to: ISODate("2023-01-21T18:30:00.000Z"),
matchStatus: 0,
matchId: null,
userId: ObjectId("ddbb8f3c59cf13467cbd6a532"),
organisationId: ObjectId("246afaf417be1cfdcf55792be"),
location: "Chertsey",
type: "DCA",
offers: [{
from: ISODate("2023-01-23T05:00:00.000Z"),
to: ISODate("2023-01-24T07:00:00.000Z"),
locations: ["Chertsey", "Walton"],
types: ["DCA", "SRV"],
}]
}, {
_id: ObjectId("id2"),
from: ISODate("2023-01-23T06:30:00.000Z"),
to: ISODate("2023-01-23T18:30:00.000Z"),
matchStatus: 0,
matchId: null,
userId: ObjectId("d6f10351dd8cf3462e3867f56"),
organisationId: ObjectId("246afaf417be1cfdcf55792be"),
location: "Chertsey",
type: "DCA",
offers: [{
from: ISODate("2023-01-21T05:00:00.000Z"),
to: ISODate("2023-01-21T07:00:00.000Z"),
locations: ["Chertsey", "Walton"],
types: ["DCA", "SRV"],
}]
}]
I want the $or to match all documents that have the corresponding from/to/location/type as the current document - the idea is two shifts that could be swapped
If the offers are known (passed as an array to the function calling aggregate), I can do this with:
$or: offers.map((x) => ({
from: { $gte: new Date(x.from) },
to: { $lte: new Date(x.to) },
location: { $in: x.locations },
type: { $in: x.types },
}))
BUT I want to be able to do this in an aggregation pipeline when the offers will only be known from the current document, $offers
Is this possible? I've tried $in, $map, $lookup, $filter, $getField but can't get it right and can't get anything from Google as it thinks I want $in (which is the opposite of what I need).
I'm pretty new to MongoDB and am probably approaching this completely wrong but I'd really appreciate any help!
Edit: expected output is simply an array of matching documents, so passing document id1 to the function would return an array with id2 in, because each document is compatible with the other
///expected output, from and to are between an offer in id1's from and to, similarly types/locations are compatible
{
_id: ObjectId("id2"),
from: ISODate("2023-01-23T06:30:00.000Z"),
to: ISODate("2023-01-23T18:30:00.000Z"),
matchStatus: 0,
matchId: null,
userId: ObjectId("d6f10351dd8cf3462e3867f56"),
organisationId: ObjectId("246afaf417be1cfdcf55792be"),
location: "Chertsey",
type: "DCA",
offers: [{
from: ISODate("2023-01-21T05:00:00.000Z"),
to: ISODate("2023-01-21T07:00:00.000Z"),
locations: ["Chertsey", "Walton"],
types: ["DCA", "SRV"],
}]
You can perform self-lookup with your criteria set in the sub-pipeline.
db.collection.aggregate([
{
$match: {
organisationId: "organisationId1",
matchId: null,
matchStatus: 0
}
},
{
$unwind: "$offers"
},
{
"$lookup": {
"from": "collection",
"let": {
offersFrom: "$offers.from",
offersTo: "$offers.to",
offersLocation: "$offers.locations",
offersType: "$offers.types"
},
"pipeline": [
{
$match: {
$expr: {
$and: [
{
$gte: [
"$from",
"$$offersFrom"
]
},
{
$lte: [
"$to",
"$$offersTo"
]
},
{
"$in": [
"$location",
"$$offersLocation"
]
},
{
"$in": [
"$type",
"$$offersType"
]
},
]
}
}
}
],
"as": "selfLookup"
}
},
{
"$unwind": "$selfLookup"
},
{
"$replaceRoot": {
"newRoot": "$selfLookup"
}
}
])
Mongo Playground
My MongoDB schema (simplified):
user: ObjectID
calories: Number
meals:[{
calories: Number
name:String
}]
And I have a updateMany query:
await Meals.updateMany(
{ user: user, 'meals.name': extraMealName },
{ $inc: { calories: 'meals.$.calories' } },
{multi : true},
function(error, result) {
console.log(error);
}
);
The query throws me this error:
CastError: Cast to Number failed for value "meals.$.calories" at path "calories"
I have tried changing the query for the last hour, but nothing worked... I also browsed stackoverflow, but found nothing I could work with
Does someone have an idea how to fix this?
Using pipelined update,
$reduce, go through the meals array and add up the calories where name=extraMealName
$subtract from calories, the sum from previous step
mongoplayground
db.Meals.update({
user: "user", "meals.name": "extraMealName"
},
[
{
$set: {
calories: {
$subtract: [
"$calories",
{
$reduce: {
input: "$meals",
initialValue: 0,
in: {
$add: [
"$$value",
{
$cond: [
{$eq: ["$$this.name", "extraMealName"]},
"$$this.calories",
0
]
}
]
}
}
}
]
}
}
}
]);
Updated for multiple fields.
db.collection.update({
user: "user", "meals.name": "extraMealName"
},
[
{
$addFields: {
reducedValues: {
$reduce: {
input: "$meals",
initialValue: {
calories: 0, fat: 0
},
in: {
calories: {
$add: [
"$$value.calories",
{
$cond: [
{$eq: ["$$this.name", "extraMealName"]},
"$$this.calories",
0
]
}
]
},
fat: {
$add: [
"$$value.fat",
{
$cond: [
{$eq: ["$$this.name", "extraMealName"]},
"$$this.fat",
0
]
}
]
}
}
}
}
}
},
{
$set: {
"calories": {
$subtract: ["$calories", "$reducedValues.calories"]
},
"fat": {
$subtract: ["$fat", "$reducedValues.fat"]
},
}
}
]);
Playground
the $inc has a syntax error, $inc expects a number not string so try some like this.
await Meals.updateMany(
{ user: user, 'meals.name': extraMealName },
{ $inc: { calories: { $sum: '$meals.$.calories' } } },
{ multi: true },
function(error, result) {
console.log(error);
}
);
I have collection: bookSchema as:
[
{
_id: ObjectId("637d05dc32428ed75ea08d09"),
book_details: {
book_name: "random123",
book_auth: "Amber"
}
},
{
_id: ObjectId("637d0673ce0f17f6c473dee2"),
book_details: {
book_name: "random321",
book_auth: "Amber"
}
},
{
_id: ObjectId("637d069a3d597c8458ebe4ec"),
book_details: {
book_name: "random676",
book_auth: "Amber"
}
},
{
_id: ObjectId("637d06c05b32d503007bcb54"),
book_details: {
book_name: "random999",
book_auth: "Saurav"
}
}
]
Desired O/P to show as:
{
score_ambr: 3,
score_saurabh: 1
}
For this I tried as:
db.bookSchema.aggregate([
{
"$group": {
"_id": {
"$eq": [
"$book_details.book_auth",
"Amber"
]
},
"score_ambr": {
"$sum": 1
}
},
},
{
"$group": {
"_id": {
"$eq": [
"$book_details.book_auth",
"Saurav"
]
},
"score_saurabh": {
"$sum": 1
}
},
}
])
I tried using $group to as I want to group all the matching documents in one and use $count to give the number of count for the matching documents but it doesn't seem to be working and gives the O/P as
O/P:
[
{
"_id": false,
"score_sau": 2
}
]
MongoDB Playground: https://mongoplayground.net/p/cZ64KwAmwlv
I don't know what mean 3 and 1 in your example but if I've understood correctly you can try this query:
The trick here is to use $facet to create "two ways" in the aggregation. One option will filter by Amber and the other one by Saurav.
And then, as values are filtered, you only need yo know the size of the array generated.
db.collection.aggregate([
{
"$facet": {
"score_ambr": [
{
"$match": {
"book_details.book_auth": "Amber"
}
}
],
"score_saurabh": [
{
"$match": {
"book_details.book_auth": "Saurav"
}
}
]
}
},
{
"$project": {
"score_ambr": {
"$size": "$score_ambr"
},
"score_saurabh": {
"$size": "$score_saurabh"
}
}
}
])
Example here
Note that in this way you avoid to use $group.
It looks like what you want is two group twice and create a dynamic key from the book_details.book_auth:
db.bookSchema.aggregate([
{$group: {_id: "$book_details.book_auth", count: {$sum: 1}}},
{$group: {
_id: 0,
data: {$push: {
k: {$concat: ["score_", {$toLower: "$_id"}]},
v: {$sum: "$count"}
}}
}},
{$replaceRoot: {newRoot: {$arrayToObject: "$data"}}}
])
See how it works on the playground example
I'm trying to perform a look up which works fine and in the correct document as 'metrics'. The lookup document has an array inside of its object called 'history'. I'm trying to unwind that history and perform a facet on it, an aggregation query that I have directly on the lookup collection that works fine.
However when using it here it's not returning anything. Am I unwinding this incorrectly? should it be $metrics.history ?
{
from: 'historicprices',
localField: 'collectibleId',
foreignField: 'collectibleId',
pipeline: [
{$set: {"target-date": "$$NOW"}},
{$unwind: {path: "$history"}},
{$facet: {
"one_day": [
{ $match: { $expr: { $lte: [{$subtract: ["$target-date", "$history.date" ]}, {$multiply: [24,60,60,1000] }] } } },
{ $group: { _id: null, "first": { $first: "$history.value" }, "last": { $last: "$history.value" }, "min-price": {"$min": "$history.value"}, "max-price": {"$max": "$history.value"} } },
{ $unset: ["_id"]}
]
"one_week": [
{ $match: { $expr: { $lte: [{$subtract: ["$target-date", "$history.date" ]}, {$multiply: [7, 24, 60, 60, 1000] }] } } },
{ $group: { _id: null, "min-price": {"$min": "$history.value"}, "first": { $first: "$history.value" }, "last": { $last: "$history.value" }, "max-price": {"$max": "$history.value"} } },
{ $unset: ["_id"]}
]
}}
],
as: 'metrics',
}
Thanks
Maybe you need to add preserveNullAndEmptyArrays: true to your unwind stage. Otherwise no data will be received if one of the docs doesn't return this field.
I am new to MongoDB and I am stuck in the below scenario.
I have a collection that contains duplicate docs.
I just want to get the sum of the property in each doc excluding the duplicate docs.
My Docs looks like this:
{"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":10,
"transactionId":"319723fb80b1a297cf0803abad9bc60787537f14a6a37d6e47",
"uid":"2019-11-20T17:39:17.405Z",
"createdAt":"2019-11-21T08:56:56.589+00:00",
"updatedAt":"2019-11-21T08:56:56.589+00:00","__v":0
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
{
"_id":"5dd629461fc50b782479ea90",
"referenceId":"5dd581f10859d2737965d23a",
"sellingId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"account_name":"mrfsahas1234",
"vendor_name":"testaccount2",
"action_type":"purchase",
"product_name":"Bottle",
"product_quantity":50,
"transactionId":"320a9a2f814a45e01eb98344c9af708fa2864d81587e5914",
"uid":"2019-11-20T17:39:17.405Z",
},
Currently, I am doing this:
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"}}
},
])
It returns the sum of product_quantity all the matching docs (including the duplicate docs).
Current Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:110}
Expected Output:
{_id: "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914", PurchseQuantity:60}
I want to get the sum of only unique docs. How can I achieve it?
Thanks in advance!
You need to sum inside of the $group _id field, and then use the replaceRoot to achieve the the result you wanted.
MaterialsTrack.aggregate([
{
$match: {
$and: [
{
product_name: "Bottle"
},
{
account_name: "mrfsahas1234"
},
{
action_type: "purchase"
},
{
uid: "2019-11-20T17:39:17.405Z"
}
]
}
},
{
$group: {
_id: {
sellingId: "$sellingId",
PurchaseQuantity: {
$sum: "$product_quantity"
}
}
}
},
{
$replaceRoot: {
newRoot: {
_id: "$_id.sellingId",
PurchaseQuantity: "$_id.PurchaseQuantity"
}
}
}
]);
Sample Output:
[
{
"PurchaseQuantity": 50,
"_id": "320a9a2f814a45e01eb98344c9af708fa2864d81587e5914"
}
]
Playground:
https://mongoplayground.net/p/MOneCRiSlO0
What about adding $addToSet to your aggregations pipeline
MaterialsTrack.aggregate([
{
$match: {
$and: [
{product_name: product_name},
{account_name: account_name},
{action_type: 'purchase'},
{uid:uid}
]
}
},
{
$group: {_id: "$sellingId", PurchseQuantity: {$sum: "$product_quantity"},"list" : {$addToSet : "$list"}}
},
])