Basically I am trying to find the average rating out of 5 based on comments for specific movie titles in a small app using Node.js and Mongoose.
Movie Schema:
var mongoose = require("mongoose");
//SCHEMA SET UP
var movieSchema = new mongoose.Schema({
title: String,
director: String,
year: Number,
comments:[
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Movie", movieSchema);
Comment Schema:
var mongoose = require("mongoose");
var commentSchema = mongoose.Schema({
author: String,
text: String,
rating: Number,
movie: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Movie"
}
]
});
module.exports = mongoose.model("Comment", commentSchema);
Trying to find the average movie rating:
Comment.aggregate([
{
$group: {
_id: '$movie',
ratingAvg: {$avg: '$rating'}
}
}
], function(err, results){
if(err){
console.log(err);
}else{
console.log(results);
}
}
);
Based on 3 sample ratings(5, 5, 4) on two separate movies this will return:
[ { _id: 'movie', ratingAvg: 4.666666666666667 } ]
So it is getting me the average of ALL the comments, but I want the average depending on the title of the movie. So for example I have a movie with the title of "Big Trouble in Little China" and it has two comments with the ratings of 5 and 4. I want it to return an average of 4.5. Sorry if this question is confusing, I'm super new to MongoDB and Mongoose and I think its a little over my head I'm afraid :(.
Edit: Here are some sample documents from the movie collection:
{ "_id" : ObjectId("57b135aefaa8fcff66b94e3e"), "title" : "Star Trek", "director" : "Idu No", "year" : 2008, "comments" : [ ObjectId("57b
135b9faa8fcff66b94e3f"), ObjectId("57b135c5faa8fcff66b94e40") ], "__v" : 2 }
{ "_id" : ObjectId("57b137b0a64ba6296d0df2d0"), "title" : "Buntley", "director" : "Lucy Mayfield", "year" : 2016, "comments" : [ ObjectId
("57b137bca64ba6296d0df2d1"), ObjectId("57b137caa64ba6296d0df2d2") ], "__v" : 2 }
and the comment collection:
{ "_id" : ObjectId("57b135b9faa8fcff66b94e3f"), "author" : "Tyler", "text" : "Ok", "rating" : 5, "movie" : [ ], "__v" : 0 }
{ "_id" : ObjectId("57b135c5faa8fcff66b94e40"), "author" : "Jennicaa", "text" : "asdlfjljsf", "rating" : 1, "movie" : [ ], "__v" : 0 }
{ "_id" : ObjectId("57b137bca64ba6296d0df2d1"), "author" : "Bentley", "text" : "Its amazing", "rating" : 5, "movie" : [ ], "__v" : 0 }
{ "_id" : ObjectId("57b137caa64ba6296d0df2d2"), "author" : "Lucy", "text" : "Its pretty good", "rating" : 4, "movie" : [ ], "__v" : 0 }
I'm noticing that the comment collection is not associating the movie id as it is just an empty array. That must be my issue?
I just want the average of the reviews for each movie by title. So if a movie titled Star Trek had four reviews with scores of 3, 3, 4, 5, the average would be 3.75. Normally I would just sum all the numbers in an array and then divide by the length of the array, but I just don't know enough with mongoDB/mongoose to do that :(
Sorry for not providing the proper amount of information straight away.
Since the comment model has an array which holds the movies, you need to unwind the array field first before calculating aggregates based on that group. Your aggregation pipeline would have an $unwind step before the $group
to flatten the array (denormalize it). You can then group the flattened documents by the movie _id and calculate the average.
The following example shows this:
Comment.aggregate([
{ "$unwind": "$movie" },
{
"$group": {
"_id": "$movie",
"ratingAvg": { "$avg": "$rating" }
}
}
], function(err, results) {
if(err) handleError(err);
Movie.populate(results, { "path": "_id" }, function(err, result) {
if(err) handleError(err);
console.log(result);
});
})
Here is the solution I used to actually render the average to a template.
app.get("/movies/:id", function(req, res){
Movie.findById(req.params.id).populate("comments").exec(function(err, showMovie){
if(err){
console.log(err);
} else{
var total = 0;
for(var i = 0; i < showMovie.comments.length; i++) {
total += showMovie.comments[i].rating;
}
var avg = total / showMovie.comments.length;
res.render("show", {movie: showMovie, ratingAverage: avg});
}
});
});
Related
So I have this schema for a Supplier:
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Address = require('./Address.js'),
AddressSchema = mongoose.model('Address').schema,
Product = require('./Product.js'),
ProductSchema = mongoose.model('Product').schema;
// Create a new schema for the reviews collection with all the relevant information for a review
var Schema = mongoose.Schema;
var Supplier = new Schema(
{
name: String,
address: AddressSchema,
location: {
type: {type:String, default: 'Point'},
coordinates: [Number] // [<longitude>, <latitude>]
},
products: [ProductSchema]
}
);
Supplier.index({location: '2dsphere'});
var SupplierModel = mongoose.model('Supplier', Supplier );
// export the review model
module.exports = SupplierModel;
Products in my system have a "verified" field which is a boolean. In one of my routes I would like to query the DB to find all the suppliers which have products which aren't verified such that I can then render those products in the page.
I tried this, but unofrtunatelly it returns all the subdocuments no matter if "verified" is true or false:
exports.admin_accept_product_get = function (req, res) {
Supplier.find({'products.verified' : false}, function(err, docs) {
res.render('admin_accept_product', { user : req.user, suppliers: docs });
});
};
Any help is appreciated
Edit:
The previous query would return the following data:
{
"_id" : ObjectId("5b2b839a2cf8820e304d7413"),
"location" : {
"type" : "Point",
"coordinates" : [
-16.5122377,
28.4028329
]
},
"name" : "Tienda1",
"products" : [
{
"verified" : true,
"_id" : ObjectId("5b2b83d32cf8820e304d7420"),
"title" : "Vodka",
"inStock" : 15,
"typeOfItem" : "alcohol",
"sellingPrice" : 15,
"image" : "public/upload/15295784515201529168557789bottle.png",
"typeOfAlcohol" : "vodka"
},
{
"verified" : false,
"_id" : ObjectId("5b2b848f8c59960c44df09cd"),
"title" : "Whisky",
"inStock" : 40,
"typeOfItem" : "alcohol",
"sellingPrice" : 15,
"image" : "public/upload/15295786395491529323314298whisky.png",
"typeOfAlcohol" : "whisky"
}
],
"__v" : 2
}
I would like my query to not return the firt product because "verified == true"
You need to use $elemMatch to find the document and $elemMatch for projection of the data
db.collection.find({
products: {
$elemMatch: {
verified: false
}
}
},
{
products: {
$elemMatch: {
verified: false
}
},
location: 1
})
Output
[
{
"_id": ObjectId("5b2b839a2cf8820e304d7413"),
"products": [
{
"_id": ObjectId("5b2b848f8c59960c44df09cd"),
"image": "public/upload/15295786395491529323314298whisky.png",
"inStock": 40,
"sellingPrice": 15,
"title": "Whisky",
"typeOfAlcohol": "whisky",
"typeOfItem": "alcohol",
"verified": false
}
]
}
]
Check it here
--NodeJS, Mongoose, MongoDB, ExpressJS, EJS--
My website is, there is a login user, where they can submit "name" and "image" of what they want, then the author and other people can comment on that picture. On per each image, i added a function where i count the comments using the .length function which is counting the comments on the picture and projects the number of comments to my ejs file.
here is my schema:
var testSchema = new mongoose.Schema({
name: String,
image: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Loginuser"
},
username: String
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
var commentSchema = new mongoose.Schema ({
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Loginuser"
},
username: String
},
text: String,
date: String
});
var loginSchema = new mongoose.Schema ({
username: String,
password: String
});
var TestData = mongoose.model("User", testSchema);
var Comment = mongoose.model("Comment", commentSchema);
var LoginUser = mongoose.model("Loginuser", loginSchema);
I have a function that deletes a comment on User,
app.delete("/index/:id/comments/:comment_id", function(req, res){
Comment.findByIdAndRemove(req.params.comment_id, function(err){
if(err) {
console.log(err);
res.redirect("/index/");
} else {
console.log("Comment successfully deleted!");
res.redirect("back");
}
});
});
Here's some sample data from my mongoDB
commentSchema
{ "_id" : ObjectId("57d316e506d8e9186c168a49"),
"text" : "hey baby! why cry?", "
__v" : 0,
"author" : { "id" : ObjectId("57d148acd0f11325b0dcd3e6"),
"username" :
"nagy" },
"date" : "9/10/2016 , 8:07 AM" }
{ "_id" : ObjectId("57d316f706d8e9186c168a4a"),
"text" : "don't you cry baby!",
"__v" : 0,
"author" : { "id" : ObjectId("57d095d727e6b619383a39d0"),
"username": "doge" },
"date" : "9/10/2016 , 8:07 AM" }
{ "_id" : ObjectId("57d3170306d8e9186c168a4b"),
"text" : "wow so cute!!!!!!", "_
_v" : 0,
"author" : { "id" : ObjectId("57d095d727e6b619383a39d0"),
"username" : "doge" }, "date" : "9/10/2016 , 8:07 AM" }
and here's my data on testSchema
{ "_id" : ObjectId("57d316c506d8e9186c168a47"),
"name" : "Baby crying",
"image": "https://s-media-cache-ak0.pinimg.com/564x/d0/bb/ed/d0bbed614353534df9a3be0abe
5f1d78.jpg",
"comments" : [ ObjectId("57d316e506d8e9186c168a49"), ObjectId("57d3
16f706d8e9186c168a4a") ], "author" : { "id" : ObjectId("57d095d727e6b619383a39d0
"), "username" : "doge" }, "__v" : 2 }
{ "_id" : ObjectId("57d316dc06d8e9186c168a48"),
"name" : "Maria?! OZawa?!",
"image" : "https://dncache-mauganscorp.netdna-ssl.com/thumbseg/1092/1092126-bigthumb
nail.jpg",
"comments" : [ ObjectId("57d3170306d8e9186c168a4b") ], "author" : { "
id" : ObjectId("57d148acd0f11325b0dcd3e6"), "username" : "nagy" }, "__v" : 1 }
It is working fine, it's deleting the comment. The problem here is, it is deleting only on the "Comment" model.
I want to delete that same comment also on "TestData" model, because everytime i delete a comment, the count of comments remains the same.
So basically i want to delete that specific comment on both models.
I tried using this approach:
app.delete("/index/:id/comments/:comment_id", function(req, res){
TestData.findByIdAndRemove(req.params.comment_id)function(err){
if(err) {
console.log(err);
res.redirect("/index");
} else {
res.redirect("back");
}
});
});
but it isn't working.
Can you help me on what specific query should i use?
Try the following code:-
TestData.update(
{},
{$pull: {comments: req.params.comment_id}},
{ multi: true },
function(err, data){
console.log(err, data);
});
Hope this will help you.
I have a mongoose model like this:
var activityItem = mongoose.Schema({
timestampValue: Number,
xabc: String,
full: Boolean,
comp: Boolean
});
var ABC = mongoose.Schema({
activity: [activityItem],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
username: String
});
I want to get the activityItem array elements that have a timestampValue less than a specific value. Also, I want to sort the activity array first according to the timestampValue
This is the code that I currently have. And it doesn't work.
UserActivity.findOne({
'user': current_user,
'activity' : {
$all: [
{
"$elemMatch": {
timestampValue: {
$lte: time
}
}
}
]
}
},
function(err, user){
})
Sample Document structure:
{
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"user" : ObjectId("56bf225342e662f4277ded73"),
"notifications" : [],
"completed" : [],
"activity" : [
{
"timestampValue": 1456902600000,
"xabc": "Some value",
"full": true,
"comp": false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d2")
},
{
"timestampValue": 1456702600000,
"xabc": "Some other value",
"full": true,
"comp": false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d3")
}
],
"__v" : 1
}
The POST call has the following params
hash: "2e74aaaf42aa5ea733be963cb61fc5ff"
time: 1457202600000
hash comes into the picture once i have the docs from mongo
time is a unix timestamp value.
Instead of returning only the elements that are less than the time value, it is returning all the array elements. I tried the aggregation framework to sort the array before querying, but couldn't get the hang of it.
Any help would be greatly appreciated.
Please try to do it through aggregation as below
ABS.aggregate([
// filter the document by current_user
{$match: {user: ObjectId(current_user)}},
// unwind the activity array
{$unwind: '$activity'},
// filter the timestampValue less than time
{$match: {'activity.timestampValue': {$lte: time}}},
// sort activity by timestampValue in ascending order
{$sort: {'activity.timestampValue': 1}},
// group by _id, and assemble the activity array.
{$group: {_id: '$_id', user: {$first: '$user'},activity: {$push: '$activity'}}}
], function(err, results){
if (err)
throw err;
// populate user to get details of user information if needed
//ABS.populate( results, { "path": "user" }, function(err, rets) {
//
//});
});
Well, it seems little bit tricky with MongoDb aggregation pipeline unless you have MongoDB 3.2, but you can definitely
achieve your result with help of map-reduce.
e.g.
MongoDB version < 3.2
var findActivities = function (time) {
db.col.mapReduce(function () {
var item = Object.assign({}, this);
delete item.activity;
item.activity = [];
for (var i = 0; i < this.activity.length; i++) {
if (this.activity[i].timestampValue <= time) {
item.activity.push(this.activity[i]);
}
}
emit(item._id, item);
}, function (k, v) {
return {items: v};
}, {
out: {"inline": true},
scope: {time: time}
}).results.forEach(function (o) {
printjson(o); // Or perform action as appropriate
});
};
Based your sample data when called findActivities(1456802600000), it will find and return only those documents matching criteria.
{
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"value" : {
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"user" : ObjectId("56bf225342e662f4277ded73"),
"notifications" : [
],
"completed" : [
],
"__v" : NumberInt(1),
"activity" : [
{
"timestampValue" : NumberLong(1456702600000),
"xabc" : "Some other value",
"full" : true,
"comp" : false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d3")
}
]
}
}
MongoDB version 3.2+
db.col.aggregate([
{$project:{user:1, notifications:1, completed:1, activity:{
$filter:{input: "$activity", as: "activity", cond:{
$lte: ["$$activity.timestampValue", 1456802600000]}}}}}
])
Both solutions will have same output.
I have the following mongoose query:
User.find(
{
$text: {
$search: req.query.search,
$diacriticSensitive: true
}
},
{
score: {
$meta: "textScore"
}
}, function(err, results) {
if (err) {
next(err);
return;
}
return res.json(results);
});
The query leads to the following error message:
Can't canonicalize query: BadValue extra fields in $text
When I remove the "$diacriticSensitive: true" part from the query I get no error message, but only get exact matches.
What I want is when the user searches for "USERNA", but only the user "USERNAME" is available, this result should also be shown with a low score.
I am using Mongoose 4.3.5 and MongoDB 3.0.3.
MongoDB supports this attribute, doesn't Mongoose support it?
Per doc, $diacriticSensitive option is enable in mongodb v3.2
For articles document
{ "_id" : ObjectId("56d305dcc7ce7117db9b6da4"), "subject" : "Coffee Shopping", "author" : "efg", "view" : 5 }
{ "_id" : ObjectId("56d305e9c7ce7117db9b6da5"), "subject" : "Coffee and cream", "author" : "efg", "view" : 10 }
{ "_id" : ObjectId("56d305fec7ce7117db9b6da6"), "subject" : "Baking a cake", "author" : "abc", "view" : 50 }
Here are my test codes
var ArticleSchema = new mongoose.Schema({
subject: String,
author: String,
view: Number,
score: Number,
});
ArticleSchema.index({subject: 'text'});
var article = mongoose.model('article', ArticleSchema);
function queryText() {
article
.find({$text: {$search: 'Coffee', $diacriticSensitive: true}},
{score: {$meta: 'textScore'}})
.exec(function(err, doc) {
if (err)
console.log(err);
else
console.log(doc);
});
}
Result:
{ "_id" : ObjectId("56d305dcc7ce7117db9b6da4"), "subject" : "Coffee Shopping", "author" : "efg", "view" : 5, "score" : 0.75 }
{ "_id" : ObjectId("56d305e9c7ce7117db9b6da5"), "subject" : "Coffee and cream", "author" : "efg", "view" : 10, "score" : 0.75 }
I am new to node.js.
I am having JSON object of the form
{ "_id" : ObjectId("540b03ddf1768fe562fbb715"),
"productid" : "men1",
"comments" : [ { "name" : "shiva", "text" : "Haidddsdcccccc", "_id" : ObjectId("540b03dd0570a6261e20a59e"), "date_entered" : ISODate("2014-09-06T12:53:49.658Z") },
{ "name" : "shiva", "text" : "Haidddsdcccccc", "_id" : ObjectId("540cb2be35f8145a2d296ea0"), "date_entered" : ISODate("2014-09-07T19:32:14.827Z") },
{ "name" : "shiva", "text" : "Haidddsdcccccc", "_id" : ObjectId("540cb2c335f8145a2d296ea1"), "date_entered" : ISODate("2014-09-07T19:32:19.456Z") } ] }
I want to query comments of a product after a specific time
I am using query as
var query=Product.find({"productid":obj.productid,
'comments.date_entered': {$gt: obj.timed}}, {'comments.$': 1})
I am getting only one object after the specific time i.e
{
_id: "540b03ddf1768fe562fbb715"
comments: [1]
0: {
name: "shiva"
text: "Haidddsdcccccc"
_id: "540cb2be35f8145a2d296ea0"
date_entered: "2014-09-07T19:32:14.827Z"
}
}
How to get all the comments of the product after the specified time?
Doesn't seem to be a good way to do this from what I can find out. A work-around could be doing something like this:
Product.findOne({productid: PRODUCT_ID}, {comments: 1}, function(err, product) {
var comments = product.comments.filter(function(comment) {
return comment.date_entered > DATE;
});
...
})