document structure
session document:
{
"_id" : ObjectId("5648b811a16bc2e0355b8289"),
"title" : "test",
"messages" : [
{
"authorId" : ObjectId("5648b89f2fc311bc39073993"), // <-- the users id
"created" : ISODate("2015-11-15T16:51:29.652Z"),
"message" : "<p>test</p>"
}
]
}
user document:
"_id" : ObjectId("5648b89f2fc311bc39073993"),
"pic" : "karl.png",
"profile" : {
"name" : "Karl Morrison"
}
queries
first query:
var id = '5648b811a16bc2e0355b8289'; // <-- id for the session document
var session = // <-- returns the session document
yield sessions.findOne({
_id: id,
});
second query (this is where I am stuck):
var sessionUsers =
yield new Promise(function (resolve, reject) {
users.col.aggregate([
{
$match: {
'_id': {
'$in': session.messages.authorId // <--- here I need help! Need all of the authorId's in the session document from the previous query result.
}
}
},
{
$project: {
_id: 1,
pic: 1,
name: '$profile.name'
}
}
],
function (err, res) {
if (err === null)
resolve(res);
reject(err);
});
});
question
So I am trying to get all of the authorId's into an array and feed that array into the $in of the second query, which should return an array full of elements with the fields of _id, pic and name.
So sessionUsers variable should look something like this:
[{
"_id" : ObjectId("5648b89f2fc311bc39073993"),
"pic" : "karl.png",
"name" : "Karl Morrison"
},...]
I am not sure if I understand the problem correctly, but I think you are trying to send an array of objects instead of an array of _id's behind your $match. You will need to get all _id's in a simple array and use the result. You can use lodash. Here are the docs for more information.
The code below "plucks" all _id's from your session data.
var ids = _.pluck(session.messages, 'author‌Id');
Next, change the $match part to this:
{
$match: {
'_id': {
'$in': ids
}
}
}
Related
If I have the following document in my database :
{
"_id" : MainId,
"subdoc" : [
{
"_id" : SubdocId,
"userid" : "someid",
"toupdate": false,
},
{
"_id" : SubdocId2,
"userid" : "someid2",
"toupdate": false,
}
],
"extra" : [
"extraid1",
"extraid2"
]
}
How can I update the subdocument SubdocId2 where the id (SubdocId2) must match and either SubdocId2's userid is "someid2" OR value "extraid1" exists in "extra"?
The farthest I got is:
db.coll.update({
"subdoc._id":"SubdocId2", {
$or: ["extra":{$in:["extraid1"]}, "subdoc.userid":"someid2"]
}
}, {"subdoc.$.toupdate":true})
Maybe I forgot to quote up something, but I get an error (SyntaxError: invalid property id)
Please try this :
db.coll.update(
{
$and: [{ "subdoc._id": SubdocId2 }, {
$or: [{ "extra": { $in: ["extraid1"] } },
{ "subdoc.userid": "someid2" }]
}] // subdoc._id && (extra || subdoc.userid)
}, { $set: { "subdoc.$.toupdate": true } })
In your query there are couple of syntax issues & also if you don't use $set in your update part - it replace the entire document with "subdoc.$.toupdate": true. Of course if you're using mongoose that's different scenario as mongoose will internally does add $set but when executing in shell or any client you need to specify $set. Also if SubdocId2 is ObjectId() you need to convert string to ObjectId() in code before querying database.
let collection = await Collection.findOne({ 'works._id': req.params.id }).populate('works.0.photo');
This code will populate the work subdoc in index 0, however I want it to populate the index that corresponds to req.params.id.
I want something like .populate('works.i.photo'), where i represents the index of the work which contains an _id that matches req.params.id.
I figured out how to do it, but I'm certain there's a better way.
let collection = await Collection.findOne({ 'works._id': req.params.id });
const idx = collection.works.findIndex(work => work._id == req.params.id);
collection = await collection.populate(`works.${idx}.photo`).execPopulate();
This doesn't look like the intended way of doing this. Is it possible to do it without iterating to find the index? Preferably with just a single query execution.
Assuming your data is something like this :
someCollection :
/* 1 */
{
"_id" : ObjectId("5dc9c61959f03a3d68cfb8d3"),
"works" : []
}
/* 2 */
{
"_id" : ObjectId("5dc9c72e59f03a3d68cfd009"),
"works" : [
{
"_id" : 123,
"photoId" : ObjectId("5dc9c6ae59f03a3d68cfc584")
},
{
"_id" : 456,
"photoId" : ObjectId("5dc9c6b659f03a3d68cfc636")
}
]
}
photo Collection :
/* 1 */
{
"_id" : ObjectId("5dc9c6ae59f03a3d68cfc584"),
"photo" : "yes"
}
/* 2 */
{
"_id" : ObjectId("5dc9c6b659f03a3d68cfc636"),
"photo" : "no"
}
/* 3 */
{
"_id" : ObjectId("5dc9c6c259f03a3d68cfc714"),
"photo" : "yesno"
}
Mongoose Schemas :
const photoSchema = new Schema({
_id: Schema.Types.ObjectId,
photo: String,
});
const someColSchema = new Schema({
_id: { type: Schema.Types.ObjectId },
works: [{ _id: { type: Number }, photoId: { type: Schema.Types.ObjectId, ref: 'photo' } }]
});
const someCol = mongoose.model('someCollection', someColSchema, 'someCollection');
const photoCol = mongoose.model('photo', photoSchema, 'photo');
Code :
1) Using Mongoose populate (Mongoose Populate) :
let values = someCol.find({"works._id": 123}, {_id: 0, 'works.$': 1}).populate('works.photoId').lean(true).exec();
2) Using mongoDB's native $lookup (mongoDB $lookup) :
someCol.aggregate([{ $match: { 'works._id': 123 } }, { $unwind: '$works' }, { $match: { 'works._id': 123 } }, {
$lookup:
{
from: "photo",
localField: "works.photoId",
foreignField: "_id",
as: "doc"
}
}, { $project: { _id: 0, doc: { $arrayElemAt: ["$doc", 0] } } }])
Both should work similar, in aggregation we're doing $match to filter given criteria & $unwind to unwrap works array & again doing a filter to only retain values in array that match filter criteria & then doing $lookup to fetch respective document from other collection.
help me please :
a have such order collection with this schema :
const OrderSchema = new Schema(
{
doctorId: { type : Schema.Types.ObjectId, ref: 'Users'},
patientId: { type : Schema.Types.ObjectId, ref: 'Users'},
orderTime: { type : String , default:''},
createdAt: { type : Date, default:Date.now },
approvedByDoctor:{ type :Boolean, default:false },
price:{type:Number,default:0}
},
);
and a have 10 documents like this, what query must i do to get array of "orderTime" from each document? thanks
Assuming you have documents which look like this:
{
"_id" : ObjectId("578f73d17612ac41eb736641"),
"createdAt" : ISODate("2016-07-20T12:51:29.558Z")
}
{
"_id" : ObjectId("578f73e57612ac41eb736642"),
"createdAt" : ISODate("2016-07-20T12:51:49.701Z")
}
then you can generate a result document containing an array of createdAt dates which looks like this:
{ "_id" : null, "creationDates" : [ ISODate("2016-07-20T12:51:29.558Z"), ISODate("2016-07-20T12:51:49.701Z") ] }
by running the following aggregate query:
db.<your_collection>.aggregate([{$group:{"_id":null,"creationDates":{$push:"$createdAt"}}}])
this will basically group all documents in the collection ("_id":null) and push the the values from the createdAt fields into an array ("creationDates":{$push:"$createdAt"})
Use the aggregation framework to create the array. Essentially you'd want to group all the documents, use the $push accumulator operator to create the list. Follow this example to get the gist:
Order.aggregate([
{
"$group": {
"_id": 0,
"orderTimes": { "$push": "$orderTime" }
}
}
]).exec(function(err, result) {
console.log(result[0].orderTimes);
});
Below is the data fields i have in my collection.
{ "_id" : "TLa7L9HJTabD6ooLJ", "userId" : "dgS4gJtoEPoRrGE2b", "data" : { "contact" : [ "firstname", "lastname", "phone", "email", "leadsource" ] } }
i am try to replace data.contact array value with new array i am using this query
setting.update({
_id: doc.userId
}, {
$set: {
"data.contact":["data1","data2"]
}
},function(error){
if(error){
console.log(error.reason);
}else{
toastr.success('User Details updated.');
}
});
Once my above query run i got success message but in Database this value
"data.contact":["data1","data2"] still not updated.
Are you sure you are not mistaken in this line:
{
_id: doc.userId
}
shouldn't it be:
{
userId: doc.userId
}
because you have such field in database. Maybe it's just a simple mistake.
Lets say I have stream data from the Twitter API, and I have the data stored as documents in the MongoDB. What I'm trying to find is the count of screen_name under entities.user_mentions.
{
"_id" : ObjectId("50657d5844956d06fb5b36c7"),
"contributors" : null,
"text" : "",
"entities" : {
"urls" : [ ],
"hashtags" : [
{
"text" : "",
"indices" : [
26,
30
]
},
{
"text" : "",
"indices" : []
}
],
"user_mentions" : [
{
"name":"Twitter API",
"indices":[4,15],
"screen_name":"twitterapi",
"id":6253282, "id_str":"6253282"
}]
},
...
I have attempted to use map reduce:
map = function() {
if (!this.entities.user_mentions.screen_name) {
return;
}
for (index in this.entities.user_mentions.screen_name) {
emit(this.entities.user_mentions.screen_name[index], 1);
}
}
reduce = function(previous, current) {
var count = 0;
for (index in current) {
count += current[index];
}
return count;
}
result = db.runCommand({
"mapreduce" : "twitter_sample",
"map" : map,
"reduce" : reduce,
"out" : "user_mentions"
});
But its not quite working...
Since entities.user_mentions is an array, you want to emit a value for each screen_name in the map():
var map = function() {
this.entities.user_mentions.forEach(function(mention) {
emit(mention.screen_name, { count: 1 });
})
};
Then count the values by unique screen_name in the reduce():
var reduce = function(key, values) {
// NB: reduce() uses same format as results emitted by map()
var result = { count: 0 };
values.forEach(function(value) {
result.count += value.count;
});
return result;
};
Note: to debug your map/reduce JavaScript functions, you can use print() and printjson() commands. The output will appear in your mongod log.
EDIT: For comparison, here is an example using the new Aggregation Framework in MongoDB 2.2:
db.twitter_sample.aggregate(
// Project to limit the document fields included
{ $project: {
_id: 0,
"entities.user_mentions" : 1
}},
// Split user_mentions array into a stream of documents
{ $unwind: "$entities.user_mentions" },
// Group and count the unique mentions by screen_name
{ $group : {
_id: "$entities.user_mentions.screen_name",
count: { $sum : 1 }
}},
// Optional: sort by count, descending
{ $sort : {
"count" : -1
}}
)
The original Map/Reduce approach is best suited for a large data set, as is implied with Twitter data. For a comparison of Map/Reduce vs Aggregation Framework limitations see the related discussion on the StackOverflow question MongoDB group(), $group and MapReduce.