How to $match by _id provided in $project with mongodb aggregate? - node.js

I'm wanting to be able to match / filter for a specific style from whiskey.style.
I'm wondering if it's not matching due to the formatting of the OID. I tried toString() as the documentation seems to suggest - may need to investigate this more..
Here is my query / $match object
var qObj.whiskeyFilter = { whiskey: { style: '57953144abfaa62383341a72' },
_id:
{ '$in':
[ 57a115304d124a4d1ad12d81,
57a114d64d124a4d1ad12d7f,
57a1152a4d124a4d1ad12d80,
57a9049906f3733623826538 ] } }
my pipeline:
var pipeline = [
{
"$project": {
"weight": stack[0],
"whiskey": "$$ROOT",
"collection":
collect[0]
}
},
{
"$match": qObj.whiskeyFilter
}, {
"$sort": {
"weight": 1
}
}, {
"$limit": 12
}, {
"$skip": qObj.skip
}];
this works if I only include the _id / $in for the $match, but it will not $match with whiskey.style.
Here is an example of what would return from the aggregate:
[ { _id: 57a115304d124a4d1ad12d81,
weight: 1,
whiskey:
{ _id: 57a115304d124a4d1ad12d81,
name: 'sample whiskey 2',
distiller: 578c04f76091bcd618f26e04,
style: 57953144abfaa62383341a72,
userVote: 0,
timestamp: Tue Aug 02 2016 16:48:32 GMT-0500 (CDT),
total: 1,
vote: 2,
__v: 0 },
collection:
{ _id: 57acb4ff093360bee276aae6,
user: 57919ac16fa0390856a9998f,
whiskey: 57a115304d124a4d1ad12d81,
__v: 0,
collected: true,
vote: 1,
timestamp: Thu Aug 11 2016 12:25:19 GMT-0500 (CDT),
favorite: true } }
]
Update
I'm converting it into an objectId, as i've read the aggregation may have issues casting to string, but I still am not getting any returns for an expected match:
mongoose.Types.ObjectId(obj.style);
now, you can see that the style Id is no longer a string, but still $match does not seem to work:
match query : { whiskey: { style: 57953144abfaa62383341a72 },
_id:
{ '$in':
[ 57a115304d124a4d1ad12d81,
57a114d64d124a4d1ad12d7f,
57a1152a4d124a4d1ad12d80,
57a9049906f3733623826538 ] } }

Figured it out:
I had to modify and add my style to filter by in the main object / not nested
{
"$project": {
"weight": stack[0],
"whiskey": "$$ROOT",
"style": "$style", <--- added
"collection":
collect[0]
}
For some reason, it was not capable of filtering the nested $$ROOT object.
I'll add more detail if I can find it in the docs. Or, if anyone else would like to expand on the answer -- i'd prefer to accept that than my own.

Related

Second Match is working not the 1st one MongoDB Aggregation

let id = new moongoes.Types.ObjectID("some_id_here")
aggregation = PropertyDetails.aggregate([
{
$match: {
team: id,
isDeleted: { $in:[ null, "", false] }
},
$match: {
units: {
$gte: 20,
$lte: 25
}
}
}])
Using the first match works fine getting the result against ID but when trying to get between Units using the second match it's returning all the units between 20 and 25 regardless of the ID I selected in First Match
It should work like you did that. I made a test
[
{
'$match': {
'team': 12345,
'isDeleted': {
'$in': [
null, '', false
]
}
}
}, {
'$match': {
'units': {
'$gte': 20,
'$lte': 25
}
}
}
]
You can see - team 12346 is not included in the results.
But why arent you using a single stage for this?
[
{
'$match': {
'team': 12345,
'isDeleted': {
'$in': [
null, '', false
]
},
'units': {
'$gte': 20,
'$lte': 25
}
}
}
]
This would lead to the same results.

MongoDB group by aggregation query

The data I have is:
[
{ type: 'software' },
{ type: 'hardware' },
{ type: 'software' },
{ type: 'network' },
{ type: 'test' },
...
]
I want to create a MongoDB group by aggregation pipeline to return the data like this:
I only want 3 objects in result
the third object in result {_id: 'other', count: 2}, This should be the sum of counts of type other that software and hardware
[
{_id: 'software', count: 2},
{_id: 'hardware', count: 1},
{_id: 'other', count: 2},
]
This is the exact query (MongoPlayground) that you need if those data are separate documents. Just add $project stage before group and then $switch operator. (If those field data are number, you might wanna check $bucket
db.collection.aggregate([
{
"$project": {
type: {
"$switch": {
"branches": [
{
"case": {
"$eq": [
"$type",
"software"
]
},
"then": "software"
},
{
"case": {
"$eq": [
"$type",
"hardware"
]
},
"then": "hardware"
}
],
default: "other"
}
}
}
},
{
"$group": {
"_id": "$type",
"count": {
"$sum": 1
}
}
}
])
Also, I'd like to recommend avoiding field name type. Actually it doesn't reserve in MongoDB, but however it could bring conflicts with some drivers since, in schema/model files, type fields are referred to the exact BSON type of the field.

Mongoose/MongoDB Get mostly viewed articles grouped within a day

I am trying to clone a Reddit-like community board API using MongoDB + Mongoose on Node.js.
My sample JSON data looks like below:
{
"genre": "free",
"viewCount": 90,
"isDeleted": false,
"commentCount": 0,
"voteCount": 0,
"_comments": [],
"_vote": [],
"_id": "ObjectId",
"title": "blahblah",
"contents": "blah",
"createdAt": "2020-01-24T08:50:28.409Z",
"__v": 0,
"id": "5e2aafd4395bf593aa94b623"
},
To solve this problem, I simply sorted using .sort({ viewCount:-1, createdAt: -1 }).
However, when I sorted in this way, the most recently created Post will be always come first, even though other posts have larger viewCount values...
The next thing I'm thinking of is trying to group Posts data by each day (i.e. All posts created today is grouped together; All posts created yesterday is grouped together).
After grouping, then maybe I can sort the rest of data by viewCount.
I believe the method using aggregate would be the one possible solution, but I'd like to know if there would be the simplest and the best solution for this problem!
The output I'd like to get is something like this:
// viewcount in Descending Order
{ '2020-01-24':
{ post1: { viewcount: 999, contents: ...},
{ post2: { viewcount: 998, contents:... },
... } },
'2020-01-23':
{ post1: { viewcount: 999, contents: ...},
{ post2: { viewcount: 998, contents:... },
... },
'2020-01-22':
{ post1: { viewcount: 999, contents: ...},
{ post2: { viewcount: 998, contents:... },
... }, ...}
Please help me out here...
Thank you :)
This aggregation gives something similar to the output you are expecting:
db.test.aggregate( [
{ $sort: { createdAt: -1, viewCount: -1} },
{ $group: { _id: "$createdAt", post: { $push: "$$ROOT" } } },
{ $project: { post: 1, date: "$_id", _id: 0 } }
] )

Mongoose/mongodb - get only latest records per id

I have an Inspection model in mongoose:
var InspectionSchema = new Schema({
business_id: {
type: String,
required: true
},
score: {
type: Number,
min: 0,
max: 100,
required: true
},
date: {
type: Number, // in format YYYYMMDD
required: true
},
description: String,
type: String
});
InspectionSchema.index({business_id: 1, date: 1}, {unique: true});
It is possible for there to be multiple inspections on the same Business (each Business is represented by a unique business_id). However, there is a limit of one inspection per business per day, which is why there is a unique index on business_id + date.
I also created a static method on the Inspection object which, given a list of business_ids, retrieves all of the inspections for the underlying businesses.
InspectionSchema.statics.getAllForBusinessIds = function(ids, callback) {
this.find({'business_id': {$in: ids}}, callback);
};
This function fetches all of the inspections for the requested businesses. However, I want to also create a function that fetches only the latest inspection per business_id.
InspectionSchema.statics.getLatestForBusinessIds = function(ids, callback) {
// query to get only the latest inspection per business_id in "ids"?
};
How might I go about implementing this?
You can use the .aggregate() method in order to get all the latest data in one request:
Inspection.aggregate(
[
{ "$sort": { "buiness_id": 1, "date": -1 } },
{ "$group": {
"_id": "$business_id",
"score": { "$first": "$score" },
"date": { "$first": "$date" },
"description": { "$first": "$description" },
"type": { "$first": "$type" }
}}
],
function(err,result) {
}
);
Just $sort then $group with the "business_id" as the grouping key. The $first gets the first results from the grouping boundary, where we already sorted by date within each id.
If you just want the date then do this using $max:
Inspection.aggregate(
[
{ "$group": {
"_id": "$business_id",
"date": { "$max": "$date" }
}}
],
function(err,result) {
}
);
Also see $match if you want to "pre-filter" the business id values or any other conditions when doing this.
try this:
Inpection.aggregate(
[
{ $match : { _id : { "$in" : ids} } },
{ $group: { "_id" : "$business_id", lastInspectionDate: { $last: "$date" } } }
],
function(err,result) {
}
);

Mongoose Virtuals in MongoDB Aggregate

My Mongoose Schema is as follows:
var DSchema = new mongoose.Schema({
original_y: {type: Number},,
new_y: {type: Number},,
date: {type: Date},
dummy: [dummyEmbeddedDocuments]
}, toObject: { virtuals: true }, toJSON: { virtuals: true}
});
DSchema.virtual('dateformatted').get(function () {
return moment(this.date).format('YYYY-MM-DD HH:mm:ss');
});
module.exports = mongoose.model('D', DSchema);
A document in my schema would be the following:
{
id:1,
original_y: 200,
new_y: 140,
date: 2015-05-03 00:00:00.000-18:30,
dummy: [
{id:1, storage:2, cost: 10},
{id:2, storage:0, cost: 20},
{id:3, storage:5, cost: 30},
]
}
My Query:
Item.aggregate([
{
"$match": {
"dummy.storage": {"$gt": 0}
}
},
{
"$unwind": "$dummy"
},
{
"$project": {
"original_y": 1,
"new_y": 1,
"dateformatted": 1,
"dummy.id": "$dummy.id",
"dummy.storage": "$dummy.storage",
"dummy.cost": "$dummy.cost",
"dummy.tallyAmount": {
"$divide": [
{ "$add": ["$new_y","$original_y"] },
"$dummy.cost"
]
}
}
},
{
"$group": {
"_id": "_$id",
"original_y": { "$first": "$original_y" },
"dateformatted": { "$first": "$dateformatted" },
"new_y": { "$first": "$new_y" },
"dummy": {
"$addToSet": "$dummy"
}
}
}
]).exec(callback);
This query however returns the VIRTUAL dateformatted attribute as NULL. Any thoughts as to why this is happening?
A couple notes in the docs touch on why this is so:
Arguments are not cast to the model's schema because $project operators allow redefining the "shape" of the documents at any stage
of the pipeline, which may leave documents in an incompatible format.
The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
But it goes beyond this because the aggregate operation is performed server-side, where any client-side Mongoose concepts like virtuals do not exist.
The result is that you'll need to include the date field in your $project and $group stages and add your own dateformatted field to the results in code based on the date values.
This is an old question but I've come up with a useful hack to get back the virtuals and thought it might be useful for those searching for this problem.
You can easily convert the objects back to mongoose models:
documents = documents.map(d => {
return new Document(d);
});
var virtual = documents[0].virtualProperty;
the <field>: <1 or true> form is used to include an existing field which is not the case here since the dateformatted field doesn't exist and you have to create it using an expression, $dateToString can be used:
"$project": {
"original_y": 1,
"new_y": 1,
"dateformatted": { "$dateToString": { "format": "%Y-%m-%d %H:%M:%S", "date": "$date" } },
...
Another option is to use it with $addFields:
{
"$project": {
...
}
},
{
"$addFields": {
"dateformatted": { "$dateToString": {"format": "%Y-%m-%d %H:%M:%S", "date": "$date"} }
}
},
...
Here's a solution that works!
Aggregate queries return js objects which is not an instance of mongoose Document.
You may use Model.hydrate
const documents = docs.map(doc => myModel.hydrate(doc))

Resources