Get 1st & last item from array of unknown length - MongoDB - node.js

I have documents in a collection. Each document may or may not have a log field. If it does, this log field will be an array. This array is of unknown length. I've been trying to use the $slice operator here as best I can & I have gotten it to return the last item in the array with log: { $slice: -1 } or the 1st item in the array with log: { $slice: 1 } but I cannot figure out how to get both from a single db find query. My query to the db is this:
db.collection('entities').find({}, {
name: 1,
log: {
$slice: -1 // returning the last item
}
})
Is this possible with a simple find query or will I have to use an aggregation query?
I did attempt something like:
db.collection('entities').find({}, {
name: 1,
"log.0": 1,
log: {
$slice: -1
}
})
But this failed due to a conflict with $slice apparently but I imagine it would fail anyway as the log field may or may not exist.

Related

find the document which equals any one of the array element query with a non array field

mongo db schema variable
status:{
type: Number,
enum: [0,1,2,3,4,5], //0-NOT ACCEPTED,1-COMPLETED,2-PENDING
default: 0
}
status stored in db like 0 or 1 or 2. status search with user selection is array of datas like
status: {1,2}
how to get the documents which has any one the of the array element. I can't do a static search because array size can change every time
// if(status){
// query = {
// ...query,
// "status": status
// }
// }
console.log(body_status);
if(body_status){
query = {
...query,
"status": {"$in":body_status}
}
}
this works for me.
I don't know if I've understand the question but I think you want something like this:
db.collection.find({
"status": {
"$in": [
1,
2,
4
]
}
})
Example here
Please check if it works as expected or not and in this case update the question with more information.
Or maybe you want something like this:
db.collection.find({
"status": 1
})

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.

Handling errors with bulkinsert in Mongo NodeJS [duplicate]

This question already has answers here:
How to Ignore Duplicate Key Errors Safely Using insert_many
(3 answers)
Closed 5 years ago.
I'm using NodeJS with MongoDB and Express.
I need to insert records into a collection where email field is mandatory.
I'm using insertMany function to insert records. It works fine when unique emails are inserted, but when duplicate emails are entered, the operation breaks abruptly.
I tried using try catch to print the error message, but the execution fails as soon as a duplicate email is inserted. I want the execution to continue and store the duplicates. I want to get the final list of the records inserted/failed.
Error Message:
Unhandled rejection MongoError: E11000 duplicate key error collection: testingdb.gamers index: email_1 dup key: 
Is there any way to handle the errors or is there any other approach apart from insertMany?
Update:
Email is a unique field in my collection.
If you want to continue inserting all the non-unique documents rather than stopping on the first error, considering setting the {ordered:false} options to insertMany(), e.g.
db.collection.insertMany(
[ , , ... ],
{
ordered: false
}
)
According to the docs, unordered operations will continue to process any remaining write operations in the queue but still show your errors in the BulkWriteError.
I can´t make comment, so goes as answer:
is you database collection using unique index for this field, or your schema has unique attribute for the field? please share more information about you code.
From MongoDb docs:
"Inserting a duplicate value for any key that is part of a unique index, such as _id, throws an exception. The following attempts to insert a document with a _id value that already exists:"
try {
db.products.insertMany( [
{ _id: 13, item: "envelopes", qty: 60 },
{ _id: 13, item: "stamps", qty: 110 },
{ _id: 14, item: "packing tape", qty: 38 }
] );
} catch (e) {
print (e);
}
Since _id: 13 already exists, the following exception is thrown:
BulkWriteError({
"writeErrors" : [
{
"index" : 0,
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: restaurant.test index: _id_ dup key: { : 13.0 }",
"op" : {
"_id" : 13,
"item" : "envelopes",
"qty" : 60
}
}
],
(some code omitted)
Hope it helps.
Since you know that the error is occurring due to duplicate key insertions, you can separate the initial array of objects into two parts. One with unique keys and the other with duplicates. This way you have a list of duplicates you can manipulate and a list of originals to insert.
let a = [
{'email': 'dude#gmail.com', 'dude': 4},
{'email': 'dude#yahoo.com', 'dude': 2},
{'email': 'dude#hotmail.com', 'dude': 2},
{'email': 'dude#gmail.com', 'dude': 1}
];
let i = a.reduce((i, j) => {
i.original.map(o => o.email).indexOf(j.email) == -1? i.original.push(j): i.duplicates.push(j);
return i;
}, {'original': [], 'duplicates': []});
console.log(i);
EDIT: I just realised that this wont work if the keys are already present in the DB. So you should probably not use this answer. But Ill just leave it here as a reference for someone else who may think along the same lines.
Nic Cottrell's answer is right.

Mongoose find all documents where array.length is greater than 0 & sort the data

I am using mongoose to perform CRUD operation on MongoDB. This is how my schema looks.
var EmployeeSchema = new Schema({
name: String,
description: {
type: String,
default: 'No description'
},
departments: []
});
Each employee can belong to multiple department. Departments array will look like [1,2,3]. In this case departments.length = 3. If the employee does not belong to any department, the departments.length will be equal to 0.
I need to find all employee where EmployeeSchema.departments.length > 0 & if query return more than 10 records, I need to get only employees having maximum no of departments.
Is it possible to use Mongoose.find() to get the desired result?
Presuming your model is called Employee:
Employee.find({ "departments.0": { "$exists": true } },function(err,docs) {
})
As $exists asks for the 0 index of an array which means it has something in it.
The same applies to a maximum number:
Employee.find({ "departments.9": { "$exists": true } },function(err,docs) {
})
So that needs to have at least 10 entries in the array to match.
Really though you should record the length of the array and update with $inc every time something is added. Then you can do:
Employee.find({ "departmentsLength": { "$gt": 0 } },function(err,docs) {
})
On the "departmentsLength" property you store. That property can be indexed, which makes it much more efficient.
By some reason, selected answer doesn't work as for now. There is the $size operator.
Usage:
collection.find({ field: { $size: 1 } });
Will look for arrays with length 1.
use can using $where like this:
await EmployeeSchema.find( {$where:'this.departments.length>0'} )
If anyone is looking for array length is greater than 1, you can do like below,
db.collection.find({ "arrayField.1" : { $exists: true }})
The above query will check if the array field has value at the first index, it means it has more than 1 items in the array. Note: Array index start from 0.

$inc with mongoose ignored

I am using the following code to add some content in an array and increment two different counters.
The item is properly pushed in the array, and the pendingSize is properly incremented. But the unRead is never incremented. It used to increment, then today it stopped. The value of the unRead field in my mongodb collection ( hosted on mongohq ) is set to 0 (numerical, not string)
When I look in my console, I see 'update success'.
any clue why it stopped working ?
Thanks
Notifications.update({ "id" : theid}, { $push: { pending: notification}, $inc: { unRead : 1 }, $inc: { pendingSize: 1 }}, function(err){
if(err){
console.log('update failed');
callback("Error in pushing." + result.members[i]);
}
else{
console.log('update succes');
callback("Success");
}
});
Combine the args of $inc into a single nested object, like this:
$inc: { unRead : 1, pendingSize: 1 }
Objects represented in JSON are key:value, where the keys must be unique, so trying to specify multiple values for $inc won't work.

Resources