My application has "Posts" which will have stories
Now I want to select one post and only one of its stories from multiple stories in the story array
var db = req.db;
var mongo = require('mongodb');
var collection = db.get('clnPost');
var userId = req.body.userId;
var postId = req.body.postId;
var storyId = req.body.storyId;
var ObjectID = mongo.ObjectID;
var mongo = require('mongodb');
var ObjectID = mongo.ObjectID;
collection.find({ _id: ObjectId(postId), "Stories._id": ObjectID(req.body.storyId)}, function (e, docs) {
//some processing on the docs
}
I want this to return the post along with only story which has the story Id that is sent through the request, but its returning all the stories in the stories array
Here I am getting all the stories for that post, where as i need only the story with Id that matches req.body.storyId
I also tried using $elemMatch after checking this question but still got the same results
collection.find({ _id: postId}, {Stories: { $elemMatch: { _id: req.body.storyId } } }, function (e, docs) {
I also tried
collection.find({ "Stories._id":ObjectID(storyId)}, {"Stories.$":1,Stories: {$elemMatch: { "Stories._id" :ObjectID(storyId) }} } , function (e, docs) {
The structure of the post document is as follows
{
"_id": {
"$oid": "55a7847ee4b07e03cc6acd21"
},
"Stories": [
{
"userId": "743439369097985",
"story": "some story goes on here",
"_id": {
"$oid": "55c0b9367d1619a81daaefa0"
},
"Views": 29,
"Likes": []
},
{
"userId": "743439369097985",
"story": "some story goes on here",
"_id": {
"$oid": "55c0bbd97abf0b941aafa169"
},
"Views": 23,
"Likes": []
}
]
}
I also tried using $unwind
var test= collection.aggregate( //where collection =db.get('clnPost')
{ $match: { "Stories._id": ObjectID(storyId) } },
{ $project : { Stories : 1 } },
{ $unwind : "$Stories" }
);
Update 1: i am using monk and hence aggregate is not working
I can not copy your example document, so I used similar one:
{
"_id" : "55a7847ee4b07e03cc6acd21",
"Stories" : [{
"userId" : "743439369097985",
"story" : "some story goes on here",
"_id" : "55c0b9367d1619a81daaefa0",
"Views" : 29,
"Likes" : [ ]
}, {
"userId" : "743439369097985",
"story" : "some story goes on here",
"_id" : "55c0bbd97abf0b941aafa169",
"Views" : 23,
"Likes" : [ ]
}]
}
To get what you want, you need to use dollar projection operator in a way I used it here.
db.collection.find({
"Stories._id": "55c0bbd97abf0b941aafa169"
}, {
"Stories.$": 1
}).pretty()
Related
This question already has answers here:
Renaming Field Within an Array in MongoDB
(1 answer)
MongoDB rename database field within array
(8 answers)
Closed 10 months ago.
Below is the JSON file and I am trying to change the key name in offers from '_id' to 'id'
[{
"offers": [{
"i": "",
"a":
"e": 25.3,
"c": "A new concept in the ed-tech market. I can relate with the importance of the Learn By Doing philosophy. Keep up the Good Work! Definitely interested to work with you to scale the vision of the company!",
"_id": "62565340aa2519d6cc33e791"
}],
"id": "62565340aa2519d6cc33e790"
},
{
"er": "#3",
"p": "Title #3",
"p": "Idea #3",
"at": ,
"equity": 25.3,
"offers": [],
"id": "6256533baa2519d6cc33e78f"
}
]
I am new to Node js
I am able to change the 'id' key outside of every pitch but not inside 'offers' key.
Can someone please help me in this.
Try to change your code like this:
MongoClient.connect(url, function (err, db) {
if (err) throw err;
var dbo = db.db("mydb");
var coll = dbo.collection("pitches");
coll
.find({})
.sort({
_id: -1,
})
.toArray(function (err, result) {
if (err) {
response.send(err);
} else {
for (const value of res) {
for (const offer of value.offers) {
offer.id = offer._id
delete offer._id
}
}
response.statusCode = 201;
response.send(result);
}
});
});
Your answer is almost right. Use double for loop.
let result = [
{
"entrepreneur": "Yakshit#4",
"pitchTitle": "Sample Title #4",
"pitchIdea": "Sample Idea #4",
"askAmount": 1000000000,
"equity": 25.3,
"offers": [{
"investor": "Anupam Mittal",
"amount": 1000000000,
"equity": 25.3,
"comment": "A new concept in the ed-tech market. I can relate with the importance of the Learn By Doing philosophy. Keep up the Good Work! Definitely interested to work with you to scale the vision of the company!",
"_id": "62565340aa2519d6cc33e791"
}],
"_id": "62565340aa2519d6cc33e790"
},
{
"entrepreneur": "Yakshit#3",
"pitchTitle": "Sample Title #3",
"pitchIdea": "Sample Idea #3",
"askAmount": 1000000000,
"equity": 25.3,
"offers": [],
"_id": "6256533baa2519d6cc33e78f"
}
]
for (const val of result) {
val["id"] = val["_id"];
delete val["_id"];
for (const val2 of val["offers"]) {
val2["id"] = val2["_id"];
delete val2["_id"];
}
}
console.log(JSON.stringify(result));
do it in mongo query
db.collection.aggregate([
{
$match: {}
},
{
$set: {
id: "$_id",
offers: {
$map: {
input: "$offers",
as: "o",
in: { $mergeObjects: [ "$$o", { id: "$$o._id" } ] }
}
}
}
},
{
$unset: [ "_id", "offers._id" ]
}
])
mongoplayground
OPTION 1as explained here You can change key name while returning query result from mongodb using aggregation.
orginal data:
{ "_id" : 2, "name" : "Sarah", "salary" : 128000 }
{ "_id" : 3, "name" : "Fritz", "salary" : 25000 }
{ "_id" : 4, "name" : "Chris", "salary" : 45000 }
{ "_id" : 5, "name" : "Beck", "salary" : 82000 }
query:
db.employees.aggregate([
{ "$project": { "employee": "$name", "salary": 1 }}
])
query result:
{ "_id" : 2, "salary" : 128000, "employee" : "Sarah" }
{ "_id" : 3, "salary" : 25000, "employee" : "Fritz" }
{ "_id" : 4, "salary" : 45000, "employee" : "Chris" }
{ "_id" : 5, "salary" : 82000, "employee" : "Beck" }
OPTION 2
you can use lodash and _.transform as explained here
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've just tried MongoDB & NodeJS and having issue in finding specific array collections using Nodejs.
Here's the the collections I have :
{
"_id": ObjectId("552aa4da6e25e57ebde7ac6f"),
"username": "blablabla",
"email": "xxx#gmail.com",
"password": "Dummy Trip",
"itinerary": [
{
"_id": ObjectId("552c109adb616795044a919e"),
"title": "test-0",
"desc": "test-0-desc"
},
{
"_id": ObjectId("552c10b0db616795044a91a0"),
"title": "test-1",
"desc": "test-1-desc"
},
{
"_id": ObjectId("552c1128db616795044a91a1"),
"title": "test-2",
"desc": "test-2-desc"
}
]
}
So I need to find the array of itinerary that has objecId of "552c109adb616795044a919e"
In MongoDB terminal I use findOne command & positional $ operator and it works as expected, showing only the array I search for.
Command :
db.user.findOne({ "itinerary._id" : ObjectId("552c109adb616795044a919e") }, {"itinerary.$" : 1})
Result :
{
"_id" : ObjectId("552aa4da6e25e57ebde7ac6f"),
"itinerary" : [
{
"_id" : ObjectId("552c109adb616795044a919e"),
"title" : "test-0",
"desc" : "test-0-desc"
}
]}
But why when I tried to implement it in NodeJS, it shows all records instead of the specific array I search.
Here's my NodeJS :
var userCollection = db.get('user');
userCollection.findOne({ "itinerary._id" : new ObjectID("552c109adb616795044a919e") }, {"itinerary.$" : 1},function(e,userDocs){
console.log(userDocs);
});
NodeJS Result (Showing all results) :
{
_id: 552aa4da6e25e57ebde7ac6f,
username: 'blablabla',
email: 'xxx#gmail.com',
password: 'Dummy Trip',
itinerary: [
{
_id: 552c109adb616795044a919e,
title: 'test-0',
desc: 'test-0-desc'
},
{
_id: 552c10b0db616795044a91a0,
title: 'test-1',
desc: 'test-1-desc'
},
{
_id: 552c1128db616795044a91a1,
title: 'test-2',
desc: 'test-2-desc'
}
]
}
Did I miss something here ?
Note :
I'm using Node MongoDB native driver
Thanks in advance :)
I am assuming your itinerary is an array. then you will have to use $elemMatch in mongodb.More details on elemMatch.
You can try something like.
var userCollection = db.get('user');
userCollection.findOne({ "itinerary": { $elemMatch: {_id: new ObjectID("552c109adb616795044a919e")}}}, {"itinerary.$" : 1},function(e,userDocs){
console.log(userDocs);
});
Also i think for matching object id you can use mongoose.Types.ObjectId("552c109adb616795044a919e")
I have some documents with the following structure:
{
"_id": "53ad76d70ddd13e015c0aed1",
"action": "login",
"actor": {
"name": "John",
"id": 21337037
}
}
How can I make a query in Node.js that will return the number of the unique actors that have done a specific action. For example if I have a activity stream log, that shows all the actions done by the actors, and a actorscan make a specific action multiple times, how can I get the number of all the unique actors that have done the "login" action. The actors are identified by actor.id
db.collection.distinct()
db.collection.distinct("actor.id", { action: "login"})
will return all unique occiriences and then you can get count of a result set.
PS
do not forget about db.collection.ensureIndex({action: 1})
You can use aggregation framework for this:
db.coll.aggregate([
/* Filter only actions you're looking for */
{ $match : { action : "login" }},
/* finally group the documents by actors to calculate the num. of actions */
{ $group : { _id : "$actor", numActions: { $sum : 1 }}}
]);
This query will group the documents by the entire actor sub-document and calculate the number of actions by using $sum. The $match operator will filter only documents with specific action.
However, that query will work only if your actor sub-documents are the same. You said that you're identifying your actors by id field. So if, for some reason, actor sub-documents are not exactly the same, you will have problems with your results.
Consider these these three documents:
{
...
"actor": {
"name": "John",
"id": 21337037
}
},
{
...
"actor": {
"name": "john",
"id": 21337037
}
},
{
...
"actor": {
"surname" : "Nash",
"name": "John",
"id": 21337037
}
}
They will be grouped in three different groups, even though the id field is the same.
To overcome this problem, you will need to group by actor.id.
db.coll.aggregate([
/* Filter only actions you're looking for */
{ $match : { action : "login" }},
/* finally group the documents to calculate the num. of actions */
{ $group : { _id : "$actor.id", numActions: { $sum : 1 }}}
]);
This query will correctly group your documents by looking only at the actor.id field.
Edit
You didn't specify what driver you were using so I wrote the examples for MongoDB shell.
Aggregation with Node.js driver is very similar but with one difference: Node.js is async The results of the aggregation are returned in the callback. You can check the Node.js aggregation documentation for more examples:
So the aggregation command in Node.js will look like this:
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
if(err) throw err;
var collection = db.collection('auditlogs');
collection.aggregate([
{ $match : { action : "login" }},
{ $group : { _id : "$actor.id", numActions: { $sum : 1 }}} ],
function(err, docs) {
if (err) console.error(err);
console.log(docs);
// do something with results
}
);
});
For these test documents:
{
"_id" : ObjectId("53b162ea698171cc1677fab8"),
"action" : "login",
"actor" : {
"name" : "John",
"id" : 21337037
}
},
{
"_id" : ObjectId("53b162ee698171cc1677fab9"),
"action" : "login",
"actor" : {
"name" : "john",
"id" : 21337037
}
},
{
"_id" : ObjectId("53b162f7698171cc1677faba"),
"action" : "login",
"actor" : {
"name" : "john",
"surname" : "nash",
"id" : 21337037
}
},
{
"_id" : ObjectId("53b16319698171cc1677fabb"),
"action" : "login",
"actor" : {
"name" : "foo",
"id" : 10000
}
}
It will return the following result:
[ { _id: 10000, numActions: 1 },
{ _id: 21337037, numActions: 3 } ]
The aggregation framework is your answer:
db.actors.aggregate([
// If you really need to filter
{ "$match": { "action": "login" } },
// Then group
{ "$group": {
"_id": {
"action": "$action",
"actor": "$actor"
},
"count": { "$sum": 1 }
}}
])
Your "actor" combination is "unique", so all you need to do it have the common "grouping keys" under the _id value for the $group pipeline stage and count those "distinct" combinations with $sum.
Schema Definitions
Team.js
var TeamSchema = new Schema({
// Team Name.
name: String,
lead: String,
students :type: [{
block : Number,
status : String,
student : {
type: Schema.ObjectId,
ref: 'Student'
}]
});
Student.js
var StudentSchema = new Schema({
name: String,
rollNo : Number,
class : Number
});
How I can populate "student" to get output, as below:
team
{
"__v": 1,
"_id": "5252875356f64d6d28000001",
"students": [
{
"__v": 1,
"_id": "5252875a56f64d6d28000002",
block : 1,
status : joined,
"student": {
"name": Sumeeth
"rollNo" : 2
"class" : 5
}
},
{
"__v": 1,
"_id": "5252875a56f64d6d28000003",
block : 1,
status : joined,
"student": {
"name": Sabari
"rollNo" : 3
"class" : 4
}
}
],
"lead": "Ratha",
}
This is JS I use to get the document using Mongoose:
Team.findOne({
_id: req.team._id
})
.populate('students')
.select('students')
.exec(function(err, team) {
console.log(team);
var options = {
path: 'students.student',
model: 'Student'
};
Student.populate(team.students,options,function(err, students) {
console.log(students);
if (err) {
console.log(students);
res.send(500, {
message: 'Unable to query the team!'
});
} else {
res.send(200, students);
}
});
});
In my console output I get the following:
{ _id: 53aa434858f760900b3f2246,
students
[ { block : 1
status: 'joined'
_id: 53aa436b58f760900b3f2249 },
{ block : 1
status: 'joined'
_id: 53aa436b58f760900b3f2250 }]
}
And the expected output is:
{ _id: 53aa434858f760900b3f2246,
students
[ { block : 1
status: 'joined'
student :{
"name": Sumeeth
"rollNo" : 2
"class" : 5
}
},
{ block : 1
status: 'joined'
student :{
"name": Sabari
"rollNo" : 3
"class" : 4
}
}
]
}
Some one please help me where I am wrong. How should I make use of .populate, so that , I can get the entire student object and not only its id.
Reference :
Populate nested array in mongoose
I have been facing same issue. I have use this code for my rescue :
Team.findOne({_id: req.team._id})
.populate({ path: "students.student"})
.exec(function(err, team) {
console.log(team);
});
Here is a simplified version of what you want.
Basic data to set up, first the "students":
{
"_id" : ObjectId("53aa90c83ad07196636e175f"),
"name" : "Bill",
"rollNo" : 1,
"class" : 12
},
{
"_id" : ObjectId("53aa90e93ad07196636e1761"),
"name" : "Ted",
"rollNo" : 2,
"class" : 12
}
And then the "teams" collection:
{
"_id" : ObjectId("53aa91b63ad07196636e1762"),
"name" : "team1",
"lead" : "me",
"students" : [
{
"block" : 1,
"status" : "Y",
"student" : ObjectId("53aa90c83ad07196636e175f")
},
{
"block" : 2,
"status" : "N",
"student" : ObjectId("53aa90e93ad07196636e1761")
}
]
}
This is how you do it:
var async = require('async'),
mongoose = require('mongoose');
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/team');
var teamSchema = new Schema({
name: String,
lead: String,
students: [{
block: Number,
status: String,
student: {
type: Schema.ObjectId, ref: 'Student'
}
}]
});
var studentSchema = new Schema({
name: String,
rollNo: Number,
class: Number
});
var Team = mongoose.model( "Team", teamSchema );
var Student = mongoose.model( "Student", studentSchema );
Team.findById("53aa91b63ad07196636e1762")
.select('students')
.exec(function(err, team) {
console.log( team );
async.forEach(team.students, function(student,callback) {
Student.populate(
student,
{ "path": "student" },
function(err,output) {
if (err) throw err;
callback();
}
);
},function(err) {
console.log( JSON.stringify( team, undefined, 4 ) );
});
});
And it gives you the results:
{
"_id": "53aa91b63ad07196636e1762",
"students": [
{
"block": 1,
"status": "Y",
"student": {
"_id": "53aa90c83ad07196636e175f",
"name": "Bill",
"rollNo": 1,
"class": 12
}
},
{
"block": 2,
"status": "N",
"student": {
"_id": "53aa90e93ad07196636e1761",
"name": "Ted",
"rollNo": 2,
"class": 12
}
}
]
}
You really do not need the "async" module, but I am just "in the habit" as it were. It doesn't "block" so therefore I consider it better.
So as you can see, you initial .populate() call does not do anything as it expects to "key" off of an _id value in the foreign collection from an array input which this "strictly speaking" is not so as the "key" is on "student" containing the "foreign key".
I really did cover this in a recent answer here, maybe not exactly specific to your situation. It seems that your search did not turn up the correct "same answer" ( though not exactly ) for you to draw reference from.
You are overthinking it. Let Mongoose do the work for you.
Team.findOne({
_id: req.team._id
})
.populate({path:'students'})
.exec(function(err, team) {
console.log(team);
});
This will return students as documents rather than just the ids.
TL DR
const team = await Team.findById(req.team._id)
.populate("students");
team.students = await Student.populate(team.students, {path: "student"});
Context
Reading from all the answers I went testing everything and just Neil Lun's answer worked for me. The problem is it was on the path to a cb hell. So I cracked my head a little and 'refactored' to an elegant one-liner.
const foundPost = await Post.findById(req.params.id)
.populate("comments")
.populate("author");
foundPost.comments = await User.populate(foundPost.comments, {path: "author"});
My initial problem:
{
title: "Hello World",
description: "lorem",
author: {/* populated */},
comments: [ // populated
{text: "hi", author: {/* not populated */ }}
]
};
How my models basically are:
User = {
author,
password
};
Post = {
title,
description,
author: {}, //ref User
comments: [] // ref Comment
};
Comment = {
text,
author: {} // ref User
};
The output after problem solved:
{
comments: [
{
_id: "5dfe3dada7f3570b60dd977f",
text: "hi",
author: {_id: "5df2f84d4d9fcb228cd1df42", username: "jo", password: "123"}
}
],
_id: "5da3cfff50cf094c68aa2a37",
title: "Hello World",
description: "lorem",
author: {
_id: "5df2f84d4d9fcb228cd1aef6",
username: "la",
password: "abc"
}
};