How can i get array values from current field in document? - mongodb

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);
});

Related

Double aggregation with distinct count in MongoDB

We have a collection which stores log documents.
Is it possible to have multiple aggregations on different attributes?
A document looks like this in it's purest form:
{
_id : int,
agent : string,
username: string,
date : string,
type : int,
subType: int
}
With the following query I can easily count all documents and group them by subtype for a specific type during a specific time period:
db.logs.aggregate([
{
$match: {
$and : [
{"date" : { $gte : new ISODate("2020-11-27T00:00:00.000Z")}}
,{"date" : { $lte : new ISODate("2020-11-27T23:59:59.000Z")}}
,{"type" : 906}
]
}
},
{
$group: {
"_id" : '$subType',
count: { "$sum": 1 }
}
}
])
My output so far is perfect:
{
_id: 4,
count: 5
}
However, what I want to do is to add another counter, which will also add the distinct count as a third attribute.
Let's say I want to append the resultset above with a third attribute as a distinct count of each username, so my resultset would contain the subType as _id, a count for the total amount of documents and a second counter that represents the amount of usernames that has entries. In my case, the number of people that somehow have created documents.
A "pseudo resultset" would look like:
{
_id: 4,
countOfDocumentsOfSubstype4: 5
distinctCountOfUsernamesInDocumentsWithSubtype4: ?
}
Does this makes any sense?
Please help me improve the question as well, since it's difficult to google it when you're not a MongoDB expert.
You can first group at the finest level, then perform a second grouping to achieve what you need:
db.logs.aggregate([
{
$match: {
$and : [
{"date" : { $gte : new ISODate("2020-11-27T00:00:00.000Z")}}
,{"date" : { $lte : new ISODate("2020-11-27T23:59:59.000Z")}}
,{"type" : 906}
]
}
},
{
$group: {
"_id" : {
subType : "$subType",
username : "$username"
},
count: { "$sum": 1 }
}
},
{
$group: {
"_id" : "$_id.subType",
"countOfDocumentsOfSubstype4" : {$sum : "$count"},
"distinctCountOfUsernamesInDocumentsWithSubtype4" : {$sum : 1}
}
}
])
Here is the test cases I used:
And here is the aggregate result:

MongoDB - Update a field in an object of an array with multiple filter conditions

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.

How to populate a field of a subdocument which is an array element of another document?

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.

Mongodb aggregation $in field of array element

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
}
}
}

MongoDB/Mongoose orderby date and groupby user

I have a collection where multiple documents may have the same userId field. I would like to groupby userId so that I get a list of unique userIds, but also a sort by date so that each returned document is the latest document for that userId. I've done queries like this with sql, and I'm really hoping its possible with mongo.
In this example collection:
{ userId: 456, date: 5/16/1988 },
{ userId: 456, date: 5/17/1988 },
{ userId: 789, date: 5/18/1988 },
{ userId: 789, date: 5/17/1988 }
I would want to return:
{ userId: 456, date: 5/17/1988 },
{ userId: 789, date: 5/18/1988 }
Here is how you would do it in mongo. Note that it got this to work with a date format of yyyy-mm-dd.
db.collection.aggregate({
$group: {
id : '$userId',
date: { $max: '$date'}
}
})
Sources: http://docs.mongodb.org/manual/
In your question you say you want the full document returned. You can do this by returning the full document as a field in the $group operator.
db.coll.aggregate([
{$sort:{date:-1}},
{$group: {_id: "$userId", doc:{$first: "$$CURRENT"}} }
])
I created four documents like in your question with Date type as some random dates. This would give you the following result:
{
"_id" : 789,
"doc" : {
"_id" : ObjectId("53d80e246ebc37d0c33321ba"),
"userId" : 789,
"date" : ISODate("2014-07-05T04:00:00.000Z")
}
},
{
"_id" : 456,
"doc" : {
"_id" : ObjectId("53d80e246ebc37d0c33321b8"),
"userId" : 456,
"date" : ISODate("2014-07-05T04:00:00.000Z")
}
}
See http://docs.mongodb.org/manual/reference/operator/aggregation/group/#variables for more info on $$CURENT
Although they probably are the right way to go, I wasn't able to use the db.collection.aggregate methods because I need to use other things like .populate() on a Model.find in this situation. So I came up with a work around where I sort on userID and date in the find(options) like so:
{ sort: { updateDate: -1, userId: -1 } }
Then I wrote a function on the front end to extract the latest record for each user:
filterLatest: function(docs) {
var lastUserId = null;
var latestDocs = [];
docs.forEach(function(doc) {
if(lastUserId != doc.userId) latestDocs.push(doc);
lastUserId = doc.userId;
});
return latestDocs;
}