Mongo DB: How do I query by both Id and date - node.js

I am trying to do a query by 2 parameters on a mongoDb database using Mongoose. I need to query by who the document was created by and also a subdocument called events which has a date. I want to bring back all documents within a timeframe.
My query looks like this.
var earliest = new Date(2018,0,3);
var latest = new Date(2018,0,4);
Goal.find({createdBy:userId,'events.date':{$gte: earliest, $lte: latest}})
.exec(function(err,doc)){ //do stuff}
The document below is what was returned. I get everything in my database back and my date range query isn't taken into account. I'm new to Mongodb and I don't know what I am doing wrong.
[
{
_id: "5a4dac123f37dd3818950493",
goalName: "My First Goal",
createdBy: "5a4dab8c3f37dd3818950492",
__v: 0,
events:
[
{
_id: "5a4dac123f37dd3818950494",
eventText: "Test Goal",
eventType: "multiDay",
date: "2018-01-03T00:00:00.000Z",
eventLength: 7,
completed: false
},
{
_id: "5a4dac123f37dd3818950495",
eventText: "Test Goal",
eventType: "multiDay",
date: "2018-01-04T00:00:00.000Z",
eventLength: 7,
completed: false
},
{
_id: "5a4dac123f37dd3818950496",
eventText: "Test Goal",
eventType: "multiDay",
date: "2018-01-05T00:00:00.000Z",
eventLength: 7,
completed: false
}
],
startDate: "2018-01-04T00:00:00.000Z",
createdOn: "2018-01-04T00:00:00.000Z"
}
]

There is a difference between matching documents and matching "elements of an array". Your document already contains the whole array, even the values that don't match your array filter criteria. But since your document match criteria matches, the whole document is returned (with all the array entries).
If you just want the matching "elements" then use .aggregate() instead. An example on how to use aggregate for such a task is available at Mongodb find inside sub array

Related

Mongo db - how to join and sort two collection with pagination

I have 2 collections:
Office -
{
_id: ObjectId(someOfficeId),
name: "some name",
..other fields
}
Documents -
{
_id: ObjectId(SomeId),
name: "Some document name",
officeId: ObjectId(someOfficeId),
...etc
}
I need to get list of offices sorted by count of documetns that refer to office. Also should be realized pagination.
I tryied to do this by aggregation and using $lookup
const aggregation = [
{
$lookup: {
from: 'documents',
let: {
id: '$id'
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$officeId', '$id']
},
// sent_at: {
// $gte: start,
// $lt: end,
// },
}
}
],
as: 'documents'
},
},
{ $sortByCount: "$documents" },
{ $skip: (page - 1) * limit },
{ $limit: limit },
];
But this doesn't work for me
Any Ideas how to realize this?
p.s. I need to show offices with 0 documents, so get offices by documets - doesn't work for me
Query
you can use lookup to join on that field, and pipeline to group so you count the documents of each office (instead of putting the documents into an array, because you only case for the count)
$set is to get that count at top level field
sort using the noffices field
you can use the skip/limit way for pagination, but if your collection is very big it will be slow see this. Alternative you can do the pagination using the _id natural order, or retrieve more document in each query and have them in memory (instead of retriving just 1 page's documents)
Test code here
offices.aggregate(
[{"$lookup":
{"from":"documents",
"localField":"_id",
"foreignField":"officeId",
"pipeline":[{"$group":{"_id":null, "count":{"$sum":1}}}],
"as":"noffices"}},
{"$set":
{"noffices":
{"$cond":
[{"$eq":["$noffices", []]}, 0,
{"$arrayElemAt":["$noffices.count", 0]}]}}},
{"$sort":{"noffices":-1}}])
As the other answer pointed out you forgot the _ of id, but you don't need the let or match inside the pipeline with $expr, with the above lookup. Also $sortByCount doesn't count the member of an array, you would need $size (sort by count is just group and count its not for arrays). But you dont need $size also you can count them in the pipeline, like above.
Edit
Query
you can add in the pipeline what you need or just remove it
this keeps all documents, and counts the array size
and then sorts
Test code here
offices.aggregate(
[{"$lookup":
{"from":"documents",
"localField":"_id",
"foreignField":"officeId",
"pipeline":[],
"as":"alldocuments"}},
{"$set":{"ndocuments":{"$size":"$alldocuments"}}},
{"$sort":{"ndocuments":-1}}])
There are two errors in your lookup
While passing the variable in with $let. You forgot the _ of the $_id local field
let: {
id: '$id'
},
In the $exp, since you are using a variable id and not a field of the
Documents collection, you should use $$ to make reference to the variable.
$expr: {
$eq: ['$officeId', '$$id']
},

Node Js and Moongoose: How to loop into array of objects and delete a particular object and then update the whole document

My document schema is as follows:
const CollectionSchema = mongoose.Schema({
ImageCategory:{type:String,required:true},
imgDetails: [
{
_id: false,
imageUrl:{type:String},
imageName:{type:String},
imageMimeType:{type:String},
}
],
Date: {
type: String,
default: `${year}-${month}-${day}`,
},
},{timestamps: true,})
So in the database for example one document has multiple images with a single image category. What I am trying to do is I want to delete an object from imgDetails array.
Let me explain my question more precisely: imgDetails is an array
Explanation: I want to loop in imgDetails and then find (where imgageUrl === req.body.imageUrl) if its match delete that whole object which have that req.body.imageUrl and then update the document.
Please guide me on how to write such a query. Regards
Demo - https://mongoplayground.net/p/qpl7lXbKAZE
Use $pull
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
db.collection.update(
{},
{ $pull: { "imgDetails": { imageUrl: "xyz" } } }
)

mongo $push overwrites rather than adds to an array within a document

I am trying to create a historical record for updates to a document in Mongo DB via NodeJS. The document updates are only in one object within the document, so it seems like creating an array of historical values makes sense.
However, when I use the $push function with db.collection.update(), it only updates the array at the 0 index rather than add to the array.
Here is what I have:
{
_id: ID,
odds: {
spread: CURRENTSPREAD,
total: CURRENTTOTAL,
history: [
0: {
spread: PREVIOUSSPREAD1,
total: PREVIOUSTOTAL1,
date: DATEENTERED
}
]
}
}
Here is what I would like:
{
_id: ID,
odds: {
spread: CURRENTSPREAD,
total: CURRENTTOTAL,
history: [
0: {
spread: PREVIOUSSPREAD1,
total: PREVIOUSTOTAL1,
date: DATEENTERED1
},
1: {
spread: PREVIOUSSPREAD2,
total: PREVIOUSTOTAL2,
date: DATEENTERED2
},
...,
n: {
spread: PREVIOUSSPREAD-N,
total: PREVIOUSTOTAL-N,
date: DATEENTERED-N
}
]
}
}
There is no need to check whether the previous value exists before adding.
Here is my code:
var oddsHistoryUpdate = {
$push: {
'odds.history': {
spread: game.odds.spread,
total: game.odds.total,
date: Date.now()
}
}
}
db.collection('games').update({"_id": ID}, oddsHistoryUpdate).
.then(finish executing)
Why is it only pushing to the 0 index instead of adding to the array? How do I fix?
Bigga_HD's answer is the correct one regarding the $push operator. However, there may be an alternative solution that is more aligned to how MongoDB works under the hood.
A single document in MongoDB has a hard limit of 16MB, and if a document is frequently updated, it is possible that the array grows so large that it hits this limit.
Alternatively, you can just insert a new document into the collection instead of pushing the old document inside an array. The new & old documents can be differentiated by their insertion date. For example:
{
_id: ID,
name: <some identification>
insert_date: ISODate(...),
odds: {
spread: CURRENTSPREAD,
total: CURRENTTOTAL
}
}
You can then query the collection using a combination of e.g. its name and insert_date, sorted by its date descending, and limit by 1 to get the latest version:
db.collection.find({name: ...}).sort({insert_date: -1}).limit(1)
or remove the limit to find all versions:
db.collection.find({name: ...}).sort({insert_date: -1})
To support this query, you can create an index based on name and insert_date in descending order (see Create Indexes to Support Your Queries)
db.collection.createIndex({name: 1, insert_date: -1})
As a bonus, you can use a TTL index on the insert_date field to automatically delete old document versions.
$push
The $push operator appends a specified value to an array.
The $push operator has the form:
{ $push: { <field1>: <value1>, ... } }
If the field is absent in the document to update, $push adds the array field with the value as its element.
If the field is not an array, the operation will fail.
If the value is an array, $push appends the whole array as a single element. To add each element of the value separately, use the $each modifier with $push.
$each -Appends multiple values to the array field.
This should do the trick for you. Obviously, it's a very simplified example.
{ $push: { <field1>: { <modifier1>: <value1>, ... }, ... } }
let oddsHistoryUpdate = {
spread: game.odds.spread,
total: game.odds.total,
date: Date.now()
}
db.games.update(
{ _id: ID },
{ $push: { odds.history: oddsHistoryUpdate} }
)
I suggest try using Mongoose for your NodeJS - MongoDB interactions.
The answer was uncovered by dnickless.
In a previous call, I update the main odds object which I didn't realize was wiping out the history array.
Updating the previous call from
update($set: {odds: { spread: SPREAD, total: TOTAL }})
to
update($set: {"odds.spread": SPREAD, "odds.total": TOTAL})
and then making my $push call as written, all works fine.

mongoose updating a specific field in a nested document at a 3rd level

mongoose scheme:
var restsSchema = new Schema({
name: String,
menu: mongoose.Schema.Types.Mixed
});
simplfied document:
{
name: "Dominos Pizza",
menu:{
"1":{
id: 1,
name: "Plain Pizza",
soldCounter: 0
},
"2":{
id: 2,
name: "Pizza with vegetables",
soldCounter: 0
}
}
}
I'm trying to update the soldCounter when given a single/array of "menu items" (such as "1" or "2" objects in the above document) as followed:
function(course, rest){
rest.markModified("menu.1");
db.model('rests').update({_id: rest._id},{$inc: {"menu.1.soldCounter":1}});
}
once this will work i obviously will want to make it more generic, something like: (this syntax is not working but demonstrate my needs)
function(course, rest){
rest.markModified("menu." + course.id);
db.model('rests').update({_id: rest._id},{$inc:{"menu.+"course.id"+.soldCounter":1}});
}
any one can help with this one?
I looked for an answer but couldn't find nothing regarding the 3rd level.
UPDATE:
Added id to the ducument's subDocument
I think you want add all ids into sub-document, one way you can do as following.
Rest.find({_id: rest._id}, function(err, o) {
// add all ids into sub-document...
Object.keys(o.menu).forEach(function(key) {
o.menu[key].id = key;
});
o.save(function(err){ ... });
});
It seems you want to operate the key in query, I am afraid you cannot do it in this way.
Please refer to the following questions.
Mongodb - regex match of keys for subobjects
MongoDB Query Help - query on values of any key in a sub-object

In Mongoose, how to filter an array of objects

I have the following schema:
var sampleSchema = new Schema({
name: String,
dates: [{
date: Date,
duration: Number
}]
});
I'd need to filters the records according to the following rule: if one of dates is later than a given date date_begin, keep the record, otherwise, don't.
I have the impression that $gte or $lte are the function I need, but I can't find a way to use them correctly. I tried
sampleSchema.find({date_begin: {$gte: 'date'}});
or some variants of that, but I can't seem to be able to make it work. Anyone has an idea of how I am supposed to do this?
To do querying on elements inside arrays, $elemMatch is used :
SampleModel.find( { dates : { $elemMatch: { date : { $gte: 'DATE_VALUE' } } } } )
If you're using a single query condition, you can directly filter:
SampleModel.find( { 'dates.date': { $gte: 'DATE_VALUE' } } )

Resources