Mongoose/MongoDB - Sort by highest occurrence - mongodb

I am trying to sort my response based on the highest occurrence of a postID and within a date.
My collection structure:
[
{
"postID": "2",
"date": "2017-04-11 21:40:52",
},
{
"postID": "1",
"date": "2017-04-11 21:40:52",
},
{
"postID": "2",
"date": "2017-04-11 21:40:52",
},
{
"postID": "2",
"date": "2017-04-11 21:40:52",
},
]
So in this case, the highest occurrence is the postID: 2. So I want something like:
{
postID: 2,
postID: 1
}
Note: Remember that I need to search in between two dates as well.
Sorry if this is too obvious. I don't even know how to begin making the Mongo's/mongoose' find search.
My Stack:
Node with mongoose
MongoDB 3.2.5
Any input will help. Thanks

You can use the MongoDB aggregation framework to group on a specific property. Mongoose has a low-level wrapper around the aggregation framework.
You can $group with the postID as the _id which will give you the unique values for each. Then you can $sort. You can also use $project to rename the property back to postID if you prefer:
Collection.aggregate([
{$group: {_id: "$postID"}},
{$sort: {_id: -1}},
{$project: {postID: "$_id", _id: 0}}
]);

Related

How to $unset a embedded field MongoDB?

Here my document for example:
[{
_id: ObjectId("609391f436e519039a634311"),
name: "Class A",
numOfStudents: 10,
students: [{
name: "Student A",
age: 10,
}, {
name: "Student B",
age: 10,
}]
}]
I want to update some values of class and remove some informations of all students in class. So I am using $set and $unset in updateOne, like below:
db.class.updateOne({
_id: ObjectId("609391f436e519039a634311")
}, {
$set: { something: "Something for describe" },
$unset: { "students.$[].age": "" }
})
But now, I want $set a value to something by value of another field, I have to convert above script to a pipeline like below:
db.class.updateOne({
_id: ObjectId("609391f436e519039a634311")
}, [
{
$set: { something: "$name" },
}, {
$unset: [
"students.$[].age"
]
}
])
But it didn't work, it threw an Error:
Invalid $unset :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.
Please give me a suggestion for this.
You can't use paths that we use in update operators in aggregation.
When aggregate you can only use aggregate operators, ONLY exception is the match stage that you can use query operators also.
Query1
unset age
Test code here
update(
{"_id": ObjectId("609391f436e519039a634311")},
[{"$set": {"something": "$name"}},
{"$unset": ["students.age"]}])
Query2
you can use the "$$REMOVE" system variable, if a field gets this value its removed
here is like setting all age fields to have value $$REMOVE so they are removed
Test code here
update(
{"_id": ObjectId("609391f436e519039a634311")},
[{"$set": {"something": "$name", "students.age": "$$REMOVE"}}])
Query3
from students we only keep the name (=> age is removed)
you have to write by hand all the fields you want to keep
Test code here
update(
{"_id": ObjectId("609391f436e519039a634311")},
[{"$set": {"something": "$name",
"students":
{"$map": {"input": "$students", "in": {"name": "$$this.name"}}}}}])

Scala / MongoDB - removing duplicate

I have seen very similar questions with solutions to this problem, but I am unsure how I would incorporate it in to my own query. I'm programming in Scala and using a MongoDB Aggregates "framework".
val getItems = Seq (
Aggregates.lookup(Store...)...
Aggregates.lookup(Store.STORE_NAME, "relationship.itemID", "uniqueID", "item"),
Aggregates.unwind("$item"),
// filter duplicates here ?
Aggregates.lookup(Store.STORE_NAME, "item.content", "ID", "content"),
Aggregates.unwind("$content"),
Aggregates.project(Projections.fields(Projections.include("store", "item", "content")))
)
The query returns duplicate objects which is undesirable. I would like to remove these. How could I go about incorporating Aggregates.group and "$addToSet" to do this? Or any other reasonable solution would be great too.
Note: I have to omit some details about the query, so the store lookup aggregate is not there. However, I want to remove the duplicates later in the query so it hopefully shouldn't matter.
Please let me know if I need to provide more information.
Thanks.
EDIT: 31/ 07/ 2019: 13:47
I have tried the following:
val getItems = Seq (
Aggregates.lookup(Store...)...
Aggregates.lookup(Store.STORE_NAME, "relationship.itemID", "uniqueID", "item"),
Aggregates.unwind("$item"),
Aggregates.group("$item.itemID,
Accumulators.first("ID", "$ID"),
Accumulators.first("itemName", "$itemName"),
Accumulators.addToSet("item", "$item")
Aggregates.unwind("$items"),
Aggregates.lookup(Store.STORE_NAME, "item.content", "ID", "content"),
Aggregates.unwind("$content"),
Aggregates.project(Projections.fields(Projections.include("store", "items", "content")))
)
But my query now returns zero results instead of the duplicate result.
You can use $first to remove the duplicates.
Suppose I have the following data:
[
{"_id": 1,"item": "ABC","sizes": ["S","M","L"]},
{"_id": 2,"item": "EFG","sizes": []},
{"_id": 3, "item": "IJK","sizes": "M" },
{"_id": 4,"item": "LMN"},
{"_id": 5,"item": "XYZ","sizes": null
}
]
Now, let's aggregate it using $first and $unwind and see the difference:
First let's aggregate it using $first
db.collection.aggregate([
{ $sort: {
item: 1
}
},
{ $group: {
_id: "$item",firstSize: {$first: "$sizes"}}}
])
Output
[
{"_id": "XYZ","firstSize": null},
{"_id": "ABC","firstSize": ["S","M","L" ]},
{"_id": "IJK","firstSize": "M"},
{"_id": "EFG","firstSize": []},
{"_id": "LMN","firstSize": null}
]
Now, Let's aggregate it using $unwind
db.collection.aggregate([
{
$unwind: "$sizes"
}
])
Output
[
{"_id": 1,"item": "ABC","sizes": "S"},
{"_id": 1,"item": "ABC","sizes": "M"},
{"_id": 1,"item": "ABC","sizes": "L},
{"_id": 3,"item": "IJK","sizes": "M"}
]
You can see $first removes the duplicates where as $unwind keeps the duplicates.
Using $unwind and $first together.
db.collection.aggregate([
{ $unwind: "$sizes"},
{
$group: {
_id: "$item",firstSize: {$first: "$sizes"}}
}
])
Output
[
{"_id": "IJK", "firstSize": "M"},
{"_id": "ABC","firstSize": "S"}
]
group then addToSet is an effective way to deal with your problem !
it looks like this in mongoshell
db.sales.aggregate(
[
{
$group:
{
_id: { day: { $dayOfYear: "$date"}, year: { $year: "$date" } },
itemsSold: { $addToSet: "$item" }
}
}
]
)
in scala you can do it like
Aggregates.group("$groupfield", Accumulators.addToSet("fieldName","$expression"))
if you have multiple field to group
Aggregates.group(new BasicDBObject().append("fieldAname","$fieldA").append("fieldBname","$fieldB")), Accumulators.addToSet("fieldName","expression"))
then unwind

mongoDB find all documnets in an array, but return only one per match

I want to get ONE store location in each of the zip codes...
I have got as far as getting all the stores in the zip codes,
{ "address.zip": { $in: ['10003','10038','10121','12401'] } }
How would I only return ONE per zip?
It doesn't matter which ONE, just one - any ONE...
However IF I wanted to get back ONE specific one or ONE that had some attribute in the store object, how might I do that?
Structure:
{
"store_name": "McPhersons",
"address": {
"street1": "23 N LA GRANGE RD",
"city": "LA GRANGE",
"state": "IL",
"postal_code": "60525",
"country": "USA"
}
}
You can use aggregation query here. Something like this
db.collection.aggregate([
{$match: { "address.zip": { $in: ['10003','10038','10121','12401'] } }},
{$group: {_id: "$address.zip", store_name: {$first: "$store_name"}, id: {$first: "$_id"}, address: {$first: "$address"}},
{$project: {_id: "$id", store_name: "$store_name", address: "$address"}
]);
So in the above query, we are filtering using $match, then we are grouping using the address.zip and getting only the first match and then we are projecting the data according to the original collection.

Is it possible to use` $sum` in the `$match` stage of mongo aggregation and how?

I have a gifts collection in mongodb with four items inside it. how do I query the db so that I get only gifts that the sum of their amount is less-than-or-equal-to 5500?
so for example from these four gifts in db:
{
"_id": 1,
"amount": 3000,
},
{
"_id": 2,
"amount": 2000,
},
{
"_id": 3,
"amount": 1000,
},
{
"_id": 4,
"amount": 5000,
}
The query should return the first two only:
{
"_id": 1,
"amount": 3000,
},
{
"_id": 1,
"amount": 2000,
},
I think I should use mongo aggregation? if so, what is the syntax?
I had some googling, I know how to use $sum in the $group stage, but I don't know how to use it in the $match stage. is it event possible to do so?
P.S: I assumend I should use $sum in $match, Am I supposed to group them first? if so, how do I tell mongo to make a group where the sum of amounts in that group is less-than-or-equal-to 5500?
Thanks for any help you are able to provide.
You're going the right way.
First store your $sum in a variable then filter them with $match:
db.gifts.aggregate([
{$match: {}}, // Initial query
{$group: {
_id: '$code', // Assume your gift could be grouped by a unique code
sum: {$sum: '$amount'}, // Sum all amount per group
items: {$push: '$$ROOT'} // Push all gift item to an array
}},
{$match: {sum: {$lte: 5500}}}, // Filter group where sum <= 5500
{$unwind: '$items'}, // Unwind items array to get all match field
{$replaceRoot: {newRoot: '$items'}} // Use this stage to get back the original items
])

How to find out key-value which matches to given value for a week using mongo?

I have following collection structure -
[{
"runtime":1417510501850,
"vms":[{
"name":"A",
"state":"on",
},{
"name":"B",
"state":"off",
}]
},
{
"runtime":1417510484000,
"vms":[{
"name":"A",
"state":"on",
}, {
"name":"B",
"state":"off",
}]
},{
"runtime":1417510184000,
"vms":[{
"name":"A",
"state":"off",
}, {
"name":"B",
"state":"off",
}]
},{
"runtime":1417509884000,
"vms":[{
"name":"A",
"state":"on",
}, {
"name":"B",
"state":"off",
}]
},{
"runtime":1416905084000,
"vms":[{
"name":"A",
"state":"on",
}, {
"name":"B",
"state":"off",
}]
}
]
The difference between these two documents is 5 minutes which is represented by 'runtime'.
I have many such documents.
I want to find names whose state is off for a week. The only condition is state should be off through out week (should not have single value 'on' for key state).
e.g. In above data, if name 'B' is off from one week (by considering 1417510501850 as current timestamp), then my expected output will be -
{
"name":"B",
"state":"off"
}
Currently I am doing following-
1) find documents with state 'off' which are greater than 1 week using (currentTimestamp- 60*60*24*7)
2) Apply loop to result to find name and check state.
Can anybody help to get above output??
I suppose the query should be like this
db.yourcollection.aggregate([{$unwind: "$vms"}, //unwind for convenience
{$match: {"vms.state": {$eq: "off"}, runtime: {$lt: Date.now() - 7*24*60*60*1000}}}, //filter
{$project: {name: "$vms.name", state: "$vms.state"}}]) //projection
UPDATE
This is corrected query to get only docs that didn't have "on" status for a week. It is a bit more difficult, see comments
db.yourcollection.aggregate([{$unwind: "$vms"}, //unwind for convenience
{$match: {runtime: {$lt: Date.now() - 7*24*60*60*1000}}}, //filter for a period
{$project: {_id: "$vms.name", state: "$vms.state"}}, //projection so docs will be like {_id: "a", state: "on"}
{$group: {_id: "$_id", states: {$push: "$state"}}}, //group by id to see all states in array
{$match: {states: {$eq: "off", $ne: "on"}}}, //take only docs which have state "off" and not have state "on"
{$project: {_id: "$_id", state: {$literal: "off"}}}]) //and convert to required output
To understand this query it is a good idea to add one by one pipe to aggregate function and check the result.
Hope this helps.