I have a model in mongoose that looks similar to this:
var TestSchema = new Schema({
test_username: {type: String, required: true},
test_content: {type: String},
reactions: [{
test_username: {type: String, required: true},
value: {type: Number, required: true},
sent_at: {type: Date, required: true, default: Date.now}
}],
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now}
})
it stores my Test object with many reactions in it. Each reaction contains either 1 or -1 value and different usernames.
Now I'm trying to create an endpoint that gets the Test id as an input and returns the total, summed amount from all reactions that it contains.
I started writing it as:
testRoutes.get('/:id/reactions/', functions.validateRequestsGET, function(req, res){
var testId = req.params.id;
var query = Test... //here I'm stuck
query.exec(function(err, reactions){
if(err) {
res.send(err);
return;
}
res.json(reactions);
});
});
can you give me a hint of how to create a query that could return me a json with the summed amount? something like {reactions: 17} or similar?
Try this:
Test.aggregate(
{ $match: {
_id: testId // you might want to convert this from string to ObjectId()
}},
{ $project: {
sumReactions: { $sum: "$reactions.value" }
}}
)
Take a look at group accumulators $group in documentation , good examples too.
Related
I need to query documents from two collections together on mongoose.
I am familiar with SQL query and but not familiar with mongoDB.
I have two schema for Users, Messages like following.
Users
const UserSchema = new mongoose.Schema({
name: String,
email: {type: String, unique: true},
password: String,
avatar: {type: String, default: ""},
created_at: { type: Date, default: Date.now() }
});
module.exports = mongoose.model('User', UserSchema);
Messages
const MessageSchema = new mongoose.Schema({
message: { type: String, default: "" },
from: { type: String, default: "" },
to: { type: String: default: "" },
is_read: { type: Boolean, default: false },
channel: { type: String, default: ''},
created_at: { type: Date, required: true, default: Date.now }
});
module.exports = mongoose.model('Message', MessageSchema);
I need to get messages with "is_read" is "false".
I want to get "user name" and "avatar" together.
The "from" value of message should be matched with "_id" of User.
I think this post sums it up well: Mongoose - query to get data from multiple collections
Specifically the second upvoted answer mentions similarities between sql and mongodb, and goes on to explain how to link collections in mongoose queries.
i have a problem updating field and push object on the same document in one action.
this is my simple schema looks like
var Schema = new schema({
shopName: String,
address: String
products: [productSchema]
})
var productSchema = new schema({
productName: String,
ingredients: String,
item: {
qty: {type: Number, default: 0},
carted: [
{
cartId: String,
timestamp: {type: Date, default: Date.now}
}
]
}
})
the illustration is when i add cart for a product, then i will subtract the product quantity with quantity order, and also push cart info to the 'carted' array field.
i've found that "$inc": {"products.$.item.qty": -req.body.orderQty} can subtract the quantity, but it only runs on findOneAndUpdate query.
so, is there an efficient way to handle that case?
ok just tried this, and solved my problem
productModel.update({'products._id' : productID},
{
"$inc": {"products.$.item.qty": -req.body.cartQty},
"$push": {"products.$.item.carted": {cartId: req.body.cartId, qty: req.body.cartQty}}
},function(err, docs){
if (err){
console.log(err)
}else{
console.log(docs)
}
})
Intro: I am creating a StackExchange clone using Node and Mongo to learn the language. I am currently working on the API.
I have the following 'questionSchema':
var questionSchema = new Schema({
_id : {type: String, default: shortid.generate},
title : {type: String, required: true},
question : {type: String, required: true},
user : {type: Schema.ObjectId, ref: 'User'},
points : {type: Number, default: 0},
date : {type: Date, default: Date.now},
answers : [answerSchema],
votes : [{
user: {type: Schema.ObjectId, ref: 'User', required: true},
vote: {type: Number, enum: [-1,0,1]}
}],
__v : {type: Number, select: false}
});
The idea is that when a user votes on a question the points field is incremented (or decremented) and the userid and vote added to the votes array. I have the vote array to detect if the user has already voted and prevent additional votes.
The problem: I'm having trouble actually checking if the user has voted (checking if their userid exists in the votes array). I have been playing around with adding the method 'hasVoted' to the questionSchema but:
I'm not sure how to actually make the check happen.
I'm also not sure if there is a way for me to filter the votes array during the query (at MongoDB) instead of after node gets the results.
This is my attempt at the method which I know is wrong:
//Has the user already voted on this question?
questionSchema.methods.hasVoted = function (userid, cb) {
this.votes.filter(function(vote) {
if(userid == vote._id) {
return '1';
} else {
return '0';
}
});
};
I would recommend to make vote schema like so
var voteSchema = new Schema({
user: {type: Schema.ObjectId, ref: 'User', required: true},
vote : {type: Number, required: true}
})
var questionSchema = new Schema({
_id : {type: String, default: shortid.generate},
title : {type: String, required: true},
question : {type: String, required: true},
points : {type: Number, default: 0},
date : {type: Date, default: Date.now},
answers : [answerSchema],
votes : [{type: Schema.ObjectId, ref: 'Vote', required: false}]
});
Then just get your question and go through all the votes.
QuestionSchema.findById(question.id)
.populate('votes')
.exec(function (err, question) {
// go through all the votes here
}
or query if there is an question with your user id inside the votes
QuestionSchema.find()
.and([{_id:questionId,},{votes.user:userId}])
.populate('votes') //dunno if you really have to populate i think you don't have to
.exec(function (err, user) {
// check if(user)
}
or do it like described here findOne Subdocument in Mongoose
//EDIT
or if you don't change your schema
QuestionSchema.find({votes.user:userId})
.exec(function (err, user) {
// should return ALL questions where the user id is in the votes your want a specific question do it in a and like in the example above
}
and if you only want that one element from the array you have to make a projection like described here How to find document and single subdocument matching given criterias in MongoDB collection
I'm trying to query a document based off of its subdocument data. When I do this I get no data returned. When I remove the "where" or when I query on a primitive type field within the parent it works fine. What am I missing?
My collections are broken up into separate files, here they are together for simplicity:
var PlayerSchema = new Schema({
firstName: { type: String, default: '', required: true},
lastName: { type: String, default: '', required: true},
nickname: { type: String, default: '' },
});
module.exports = mongoose.model('Player', PlayerSchema);
var GameSchema = new Schema({
winner: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
datePlayed: { type: Date, default: Date.now, required: true },
});
module.exports = mongoose.model('Game', GameSchema);
var GamePlayerSchema = new Schema({
game: { type: Schema.Types.ObjectId, ref: 'Game', required: true},
player: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
points: { type: Number, default: 0 },
place: { type: Number, default: 0 },
});
module.exports = mongoose.model('GamePlayer', GamePlayerSchema);
My query:
GamePlayerModel.find()
//.where('player.firstName').equals('Brian') // returns empty
//.where(place).equals(1) // returns correct dataset
.where('game.datePlayed').gte(startDateRange).lt(endDateRange) // returns empty
.select('game player points place')
.populate('game')
.populate('player')
.exec(function (err, gamePlayers) {
if(err) return next(err);
res.json(gamePlayers);
});
So again, if I query on a subdocument in any way it returns an empty dataset. I've tried game.datePlayed and even games.datePlayed. I'm not sure what to do. I don't need the player.firstName results, however I figured that'd be an easy thing to test to make sure the query is setup correctly.
Lastly, this is how I setup the date ranges. The date objects come out correctly, but are they possibly the wrong type?
var now = new Date();
var month = req.query.month ? parseInt(req.query.month) : now.getUTCMonth();
var year = req.query.year ? parseInt(req.query.year) : now.getUTCFullYear();
var endMonth = month+1;
if(endMonth > 11) endMonth = 0;
var startDateRange = new Date(year, month, 1);
var endDateRange = new Date(year, endMonth, 1);
Joins are not supported in MongoDB, which is what you are trying to do. The closest you can get is to filter as part of your .populate call:
.populate('game', null, {datePlayed: {$gte: startDateRange, $lt: endDateRange}})
.populate('player', null, {firstName: 'Brian'})
However what this will do is get all GamePlayer documents and only get the Game and Player subdocuments that match your criteria. If the subdocuments don't match your criteria, the GamePlayer document will still be returned with .game or .player equal to null.
You may want to reconsider your schema to be less like a SQL schema and to take advantage of the benefits of MongoDB. I'm not sure what your requirements are, but consider something like this:
var PlayerSchema = new Schema({
firstName: { type: String, default: '', required: true},
lastName: { type: String, default: '', required: true},
nickname: { type: String, default: '' },
});
var GameSchema = new Schema({
winner: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
datePlayed: { type: Date, default: Date.now, required: true },
players: [{
player: { type: Schema.Types.ObjectId, ref: 'Player', required: true},
points: { type: Number, default: 0 },
place: { type: Number, default: 0 }
}]
});
Then your query could look something like:
GameModel.find()
.where('players.place').equals(1) // This will limit the result set to include only Games which have at least one Player with a place of 1
.where('datePlayed').gte(startDateRange).lt(endDateRange)
.populate('players.player')
.exec(function (err, games) {
if(err) return next(err);
res.json(games);
});
The above example will include all Players in each returned Game, regardless of whether their place is 1, however it will only include Games which have at least one player with a place of 1.
If you want to limit the items in the players array, you might need to take it a step further and use an aggregate command to unwind the array then filter. For example:
GameModel.aggregate([
{$unwind: 'players'},
{$match: {'players.place' : 1}}
], function(err, results) {
if (err) return next(err);
res.json(results);
});
This will return a separate Game object for each Player with a place of 1. If a Game has more than one Player with a place of 1, it will return duplicate Game objects, each with a different Player.
'use strict';
var mongoose = require('mongoose'),
ItemSchema = null;
module.exports = mongoose.model('Item', {
name: {type: String, required: true},
comments: [{type: mongoose.Schema.ObjectId, ref: 'Comment'}],
rating : {type: Number}
});
'use strict';
var mongoose = require('mongoose');
module.exports = mongoose.model('Comment', {
ItemId: {type: mongoose.Schema.ObjectId, ref: 'Item'},
userId: {type: mongoose.Schema.ObjectId, ref: 'User'},
text: {type: String, required: true},
createdAt: {type: Date, required: true, default: Date.now},
rating : {type: Number, required: true},
votes: {type: Number, default: 0}
});
var Item = mongoose.model('Item', ItemSchema);
module.exports = Item;
schemas are above.. the match is not working how I think it should. Really having a hard time emulating these joins.. I'm just trying to return one item with it's comments and the average rating given for it within the comments..
Item.findOne({_id : request.params.itemId} , 'comments',function(err, item){
Comment.aggregate([
{$match: {_id: {$in: item.comments}}},
{$group: {_id: '$itemId', avgRating: {$avg: '$rating'}}}], function(err, result){
reply(result);
});
});
}
If I take out the match, it returns all items with avgRating... I'm implementing something wrong in the $match.
here's what I've tried with removing the findOne:
Comment.aggregate([
{$match: {itemId : request.params.itemId}},
{$group: {_id: '$itemId', avgRating: {$avg: '$rating'}}}], function(err, result){
reply(result);
console.log('BBBBBB', result);
});
Not sure if this is what you meant by removing the findOne, but this is also not working.. request.params is returning the correct ID. it's definitely a valid field in comments, there is a comment seeded in the DB with this matching ID...