insert array using insert many function of mongoose - node.js

This is my Schema,
var trainListSchema = new mongoose.Schema({
trainNo: Number,
trainName:String,
fromStation : String,
toStation: String,
runningDays: [{type:Number}],
trainType: String,
createdTime: {type:Date , default:Date.now}})
and this is the function used to insert multiple documents.
createTrainList: function(data) {
trainList.insertMany(data, function(err, post) {
if (err) return next(err);
console.log(post);
});
}
Now as you can se runningDays is an array. While using the above method to insert data. runningDays gets inserted as 0 elements while rest of the field gets inserted successfully.
How should I go about inserting array within array using mongoose.
Help will be appreciated.
Here is the sample JSON.
[
{
"trainNo": "10104",
"trainName": "MANDOVI EXPRESS",
"fromStation": "MAO",
"toStation": "CSTM",
"days": [
0,
1,
2,
3,
4,
5,
6
],
"trainType": "MAIL EXP"
},
{
"trainNo": "10111",
"trainName": "KONKAN KANYA EX",
"fromStation": "CSTM",
"toStation": "MAO",
"days": [
0,
1,
2,
3,
4,
5,
6
],
"trainType": "MAIL EXP"
}]

Related

Node JS, Mongoose How to query schema via aggregate?

I am new to using MongoDB and I am trying to update update my document using aggregate $set pipeline. However I have been trying this for a few days and there is still no success. I am trying to update by querying ObjectId and replacing matched result (singular) with the key value pair I specified. I tested the aggregate on Mongo Compass and it works. Does anyone know how to use aggregate for Node JS?
updateOne query I tried
let query = {"_id": ObjectId('theObjectIamUpdating')};
response = await newForm.updateOne(query, payload);
aggregate query I tried
response = await newForm.updateOne([
{$match: {"_id": ObjectId(update_id)}},
{$set: {
"data.velo": [
[1, 2, 3], [5, 7]
]
}}
]);
newForm Mongoose Schema
data: {
date: {
type: Array,
required: false,
trim: true,
},
speed: {
type: Array,
required: false,
trim: true,
},
velo: {
type: Array,
required: false,
trim: true,
}
},
calc: {
date: {
type: Array,
required: false,
trim: true,
},
speed: {
type: Array,
required: false,
trim: true,
},
velo: {
type: Array,
required: false,
trim: true,
}
}
UPDATE
I my updateOne() has succeeded, but my documents are not getting updated.
the result I got after logging response that I awaited
Data received {
acknowledged: true,
modifiedCount: 0,
upsertedId: null,
upsertedCount: 0,
matchedCount: 0
}
POST /api/compute/calculate 200 16 - 79.821 ms
Additionally, this is the MongoDB aggregate that worked when I tried on Mongo Compass
pipeline = $match > $set
**$match**
{
"_id": ObjectId('62e2295060280132dbcee4ae')
}
**$set**
{
"data.velo": [
[1, 2, 3], [5, 7]
]
}
where velo is one of the key value pairs in data, and the set result replaced only the data in data.velo.
As prasad_ mentioned in the comment section, I indeed found some mistakes with regards to my syntax of updateOne().
Correct Syntax for updateOne()
let response = await Form_csv_metadata2.updateOne({ '_id': [update_id] }, [
{//delete this line $match: {"_id": ObjectId(update_id)},
$set: {
"data.velo": [[1, 2, 3], [5, 7]]
}}
]);
As the official document has mentioned the parameters are: Query, UpdateData and Option. I made the mistake as my query was placed in my UpdateData() param (the $match). It should have been a param by itself as a query (query uses same syntax as find()). Another note is that if I were to use a pipeline aggregate, it should have been { $match: {...}, $set: {...} } instead of { {$match: {...}}, {$set: {...}} }

MongoDB query and projection on subdocument array returns also array of another document

I am trying to query an embedded subdocument and then only return an array in that subdocument via projection. After a query you can select fields that you want returned via projection. I want to use the native functionality because it is possible and the most clean way. The problem is it returns arrays in two documents.
I tried different query and projection options, but no result.
User model
// Define station schema
const stationSchema = new mongoose.Schema({
mac: String,
stationName: String,
syncReadings: Boolean,
temperature: Array,
humidity: Array,
measures: [{
date: Date,
temperature: Number,
humidity: Number
}],
lastUpdated: Date
});
// Define user schema
var userSchema = mongoose.Schema({
apiKey: String,
stations : [stationSchema]
}, {
usePushEach: true
}
);
api call
app.get('/api/stations/:stationName/measures',function(req, res, next) {
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations.$.measures': 1,
}
User.findOne(query, options)
.exec()
.then(stations => {
res.status(200).send(stations)
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
});
Expected result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
}
]
}
Actual result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
},
******************************************************
// this whole object should not be returned
{
"stationName": "office",
"measures": []
}
******************************************************
]
}
edit
The answer below with aggregation works, but I still find it odd that I would need so much code. If after my normal query I get the same result with ".stations[0].measures", instead of the whole aggregation pipeline:
.then(stations => {
res.status(200).send(stations.stations[0].measures)
})
The way I read the code, the above does exactly the same as:
const options = {'stations.$.measures': 1}
Where the dollar sign puts in the index 0 as that was the index of the station that matches the query part: stationName: "livingroom"
Can someone explain?
This is not described in terms of mongoose but this will find a particular station name in an array of stations in 1 or more docs and return only the measures array:
db.foo.aggregate([
// First, find the docs we are looking for:
{$match: {"stations.stationName": "livingroom"}}
// Got the doc; now need to fish out ONLY the desired station. The filter will
// will return an array so use arrayElemAt 0 to extract the object at offset 0.
// Call this intermediate qqq:
,{$project: { qqq:
{$arrayElemAt: [
{ $filter: {
input: "$stations",
as: "z",
cond: { $eq: [ "$$z.stationName", "livingroom" ] }
}}, 0]
}
}}
// Lastly, just project measures and not _id from this object:
,{$project: { _id:0, measures: "$qqq.measures" }}
]);
$elemMatch operator limits the contents of an array field from the query results to contain only the first element matching the $elemMatch condition.
Try $elemMatch in Select Query as below :
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations' : {$elemMatch: { 'stationName' : req.params.stationName }}
}

Handling concurrent modification w/ mongoose

I have a couple places in my application that uses mongoose where I need to handle concurrent modification.
I know there is a version '__v' in all my documents. Most everything I look at points back to this blog:
http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning
Which outlines using the __v field to protect against concurrent array modification within the document. ie:
posts.update({ _id: postId, __v: versionNumber }
, { $set: { 'comments.3.body': updatedText }})
Do mongoose use the __v field automatically in any way to validate documents when they are saved? Or do you always have to add it to your query statement?
After reviewing the Mongoose source codes, the __v is really only used for arrays, the VERSION_WHERE is set when updating the array path.
if ('$push' == op || '$pushAll' == op || '$addToSet' == op) {
self.$__.version = VERSION_INC;
}
// now handling $set, $unset
else if (/\.\d+\.|\.\d+$/.test(data.path)) {
// subpath of array
self.$__.version = VERSION_WHERE;
}
And per this answer,
var t = Test();
t.name = 'hi'
t.arr = [1, 2, 3, 4, 5, 6];
t.save(function (err, result) {
console.log(result);
// use Mongoose pull method on the array
t.arr.pull(3);
t.save(function(err2, result2) {
console.log(result2)
});
});
Results:
{ __v: 0,
name: 'hi',
_id: 53f59d2a6522edb12114b98c,
arr: [ 1, 2, 3, 4, 5, 6 ] }
{ __v: 1,
name: 'hi',
_id: 53f59d2a6522edb12114b98c,
arr: [ 1, 2, 4, 5, 6 ] }

mongooseJS can not populate order with items

I'm trying to get mongoose populate to work.
I have two models one for orders and the other for the order items.
I'm doing this purely to understand populate.
I did have two files for this but I have now got everything in one file called main.js
main.js creates the 3 items in items model.
I then try to populate the order model with the 3 items.
Output for the query now gives the correct populated result.
That is to say the output from the line
console.log(JSON.stringify(orders, null, "\t");
is now:
[
{
"_id": "55d32e4594db780b1bbb4372",
"__v": 0,
"lines": [
{
"price": 2.4,
"quantity": 5,
"_id": "55d32e4594db780b1bbb436f",
"__v": 0
},
{
"price": 3.7,
"quantity": 7,
"_id": "55d32e4594db780b1bbb4370",
"__v": 0
},
{
"price": 1.2,
"quantity": 3,
"_id": "55d32e4594db780b1bbb4371",
"__v": 0
}
]
}
]
The database however is not populating.
Below is the main.js file
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mydb');
var Schema = mongoose.Schema;
var OrderSchema = new Schema({
lines: [{type: mongoose.Schema.Types.ObjectId, ref: 'Item'}]
});
var ItemSchema = new Schema({
price: Number,
quantity: Number
});
var Order = mongoose.model('Order', OrderSchema);
var Item = mongoose.model('Item', ItemSchema);
var itemArray = [
{price: 2.4, quantity: 5},
{price: 3.7, quantity: 7},
{price: 1.2, quantity: 3}
];
Item.create(itemArray, function(err) {
if (err) {
console.log('Error creating items: ', err);
}
var order = new Order();
Item.find({}, {_id: 1}, function(err, result) {
result.forEach(function(obj) {
order.lines.push(obj._id);
});
}).exec().then(function() {
order.save(function(err) {
if (!err) {
Order.find({})
.populate('lines')
.exec(function(err, orders) {
console.log(JSON.stringify(orders, null, "\t")); // output is populated the database is still not populating.
});
}
});
} );
});
It appears there is some confusion for what the populate method is for. Essentially populate is mongoose's version of a "join". It allows a query (or a document) to automatically retrieve and "join" any associated documents and returns the modified document(s). Under the hood for every referenced value in a document that is to be populated, the populate method will execute a separate query to fetch those associated documents (generally this is by ID but can be a custom query) and then in memory it will replace the references with the retrieved documents.
It does not alter the documents in the collection at all as this is purely a fetch operation.

Confirm $addToSet adds an element

I have a function adding a bunch of data into db through $addToSet, and I require a confirmation if the data has been added or not. Since $addToSet does not add duplicates, I want to know if a duplicate has not been added (show an error to the user) or if a db entry has been added (show confirmation to user).
I basically need a callback for the $addToSet operation. Couldnt find it in the docs. New to mongodb. Would appreciate help.
_notifications.update(
{'username': data.username},
{ $addToSet: pushNotification }, function(err, docs){
console.log(docs);
if (docs == 0){
co_notifications.insert(
{'username': data.username, 'notifications': insertNotification}, function(er, doc){
});
}
},
{ upsert: true }
);
I may be missing something but the only thing I can really see is in the result of the new Batch operations API.
var mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect("mongodb://localhost/test",function(err,db) {
var col = db.collection("mytest");
var batch = col.initializeOrderedBulkOp();
batch.find({ "user": "me" }).upsert().updateOne({ "$addToSet": { "list": 5 } });
batch.execute(function(err,result) {
console.log( JSON.stringify( result, undefined, 4 ) );
});
});
On that sample listing the first time you execute the contents of "result" will dump like this:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"nInserted": 0,
"nUpserted": 1,
"nMatched": 0,
"nModified": 0,
"nRemoved": 0,
"upserted": [
{
"index": 0,
"_id": "5397ae995f04804cbeb7c663"
}
]
}
The notable keys there being "nUpserted" and the "upserted" array for each document that was created. It is a batch operation afterall even though we are doing it once.
On the second execution where it should find the existing document and also match the set member you would get this:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 0,
"nRemoved": 0,
"upserted": []
}
That shows that while the document was matched nothing was actually modified as the set member was the same. But if you change the value of what is applied in the $addToSet operation to something that doesn't exist then the response would be this:
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
This shows both the "nMatched" and "nModified" values indicating the document was actually updated by the $addToSet operation.
So that may be a possible approach

Resources