How to calculate a field in mongodb - javascript

Hello i am trying to make a caculate field in mongoDB, however i get this error:
MongoError: The dollar ($) prefixed field '$add' in '$add' is not valid for storage.
This is the code:
router.post('/char1btn', ensureAuthenticated, function(req, res) {
const {
MongoClient
} = require("mongodb");
//Replace the uri string with your MongoDB deployment's connection string.
const uri =
'mongodb+srv://test:test1234#databasetest.5f9jh.mongodb.net/Databasetest?retryWrites=true&w=majority';
const client = new MongoClient(uri);
async function run() {
try {
await client.connect();
const database = client.db("Databasetest");
const collection = database.collection("users");
//create a filter for charactername to update
const filter = {
characterimg: ""
};
// this option instructs the method to create a document if no documents match the filter
const options = {
upsert: false
};
const updateDoc = {
$set: {
health: 150,
attack: 3,
defence: 3,
endurance: 10,
characterimg: "https://i.ibb.co/MPg2SMp/Apocaliptic1.png",
},
$set: {
$add: ["$power", "$attack", "$defence", {
$devide: ["$endurance", 3]
}]
}
}
const result = await collection.updateOne(filter, updateDoc, options);
console.log(
`${result.matchedCount} document(s) matched the filter, updated ${result.modifiedCount} document(s)`,
);
} finally {
res.redirect('/main');
await client.close();
}
}
run().catch(console.dir);
})
Does anyone know how to fix this?

Try this one:
const updateDoc = [
{
$set: {
health: 150,
attack: 3,
defence: 3,
endurance: 10,
characterimg: "https://i.ibb.co/MPg2SMp/Apocaliptic1.png",
}
},
{
$set: {
result: { $sum: ["$power", "$attack", "$defence", { $divide: ["$endurance", 3] }] }
}
}
];
const result = await collection.updateOne(filter, updateDoc, options);
You need two $set stages. Otherwise $sum: [...] (or $add) will use old values or fail if fields did not exist before. Also be aware that updateDoc need to be an array, see updateOne()

Update based on comment by #WernfriedDomscheit
if you are using MongoDB version > 4.2, then you can use pipeline inside update So the update query like below:
db.users.updateOne({},
[
{
$set: {
attack: 1,
defence: 2,
endurance: 3
}
},
{
$set: {
power: {
"$add": ["$attack", "$defence", { $divide: ["$endurance", 3] }]
}
}
}
],
{ upsert: true }
);
will have the output:
{
"_id" : ObjectId("603124a22391a75d9a2ddec0"),
"attack" : 1,
"defence" : 2,
"endurance" : 3,
"power" : 4
}
So in your case:
const updateDoc = [
{
$set: {
health: 150,
attack: 3,
defence: 3,
endurance: 10,
characterimg: "https://i.ibb.co/MPg2SMp/Apocaliptic1.png",
}
},
{
$set: {
power: {
$add: ["$attack", "$defence", { $divide: ["$endurance", 3] }]
}
}
}
];

Related

Return variable from database find instead of array

I need to pull two values out of my mongoDB database and right now the code is returning an array. How do I get:
totalGuests
attendedGuests
out of the query and stored in variables so I can display them on the client?
module.exports.showEvent = async(req, res,) => {
const event = await Event.findById(req.params.id).populate('artist');
if (!event) {
req.flash('error', 'Cannot find that Event');
return res.redirect('/events');
}
res.render('events/show', { event });
const { guest_id } = req.cookies;
const lookUp = Event.collection.find({ _id: req.params.id},
{
_id: 1,
name: 1,
guests: 1,
totalGuests: {
$size: "$guests"
},
attendedGuests: {
$size: {
"$filter": {
input: "$guests",
cond: {
$eq: [
"$$this.attended",
"Y"
]
}
}
}
}
});
console.log(lookUp);

Mongodb change the order of an array

I am stumped on this one. I have an images array with in my collection, the users can rearrange the order of images on the client side and I am trying to save the new order to the database. The imagesOrder array is the new images in the new order and it only has the url so I want to match the url to the urls in the database. I am not sure how to make the index a variable or if this is possible:
this is what I have so far. my code editor shows and error on [index] so I know that is not the proper format but not sure what is:
imagesOrder.forEach((index, image) => {
const imageUrl = image.url
const index = index
Users.update({
id
}, {
$set: {
images[index]: imageUrl
}
})
});
So that is not actually the way you would do this. Basically there is no need to actually send an update request to the server for every single indexed position for the array. Also the update() method is asynchronous, so it's not something you ever put inside a forEach() which does not respect awaiting the completion of an asynchronous call.
Instead what is usually the most practical solution is to just $set the entire array content in one request. Also mocking up your imagesOrder to something practical since forEach() even actually has the signature of .forEach((<element>,><index>) => ..., which seems different to what you were expecting given the code in the question.
var imagesOrder = [
{ index: 0, url: '/one' }, { index: 1, url: '/two' }, { index: 2, url: '/three' }
];
let response = await Users.updateOne(
{ id },
{ "$set": { "images": imagesOrder.map(({ url }) => url) } }
);
// { "$set": { "images": ["/one","/two","/three"] } }
Much like the forEach() a map() does the same array iteration but with the difference that it actually returns an array generated by the processing function. This is actually what you want since all that is needed here is to simply extract the values of the url property from each object.
Note the index properties are actually already in order and really redundant here, but I'm just approximating what it sounds like you have from your question. Since an "array" actually maintains it's own order then such a property "should" be redundant and it would be advisable that your source array data actually conforms to this.
If however you managed to record such index values in a way they are actually out of order, then the best solution is to add a sort():
var imagesOrder = [
{ index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
];
let response = await Users.updateOne(
{ id },
{ "$set": {
"images": imagesOrder.sort((a,b) => a.index - b.index)
.map(({ url }) => url)
}}
);
// { "$set": { "images": ["/one","/two","/three"] } }
As for what you "attempted", it's not really benefiting you in any way to actually attempt updating each element at a given position. But if you really wanted to see it done, then again you actually would just instead build up a single update request:
var imagesOrder = [
{ index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
];
var update = { $set: {} };
for ( let { url, index } of imagesOrder.sort((a,b) => a.index - b.index) ) {
update.$set['images.'+index] = url;
}
/*
* Creates:
*
* { "$set": {
* "images.0": "/one",
* "images.1": "/two",
* "images.2": "/three"
* }}
*/
let response = await Users.updateOne({ id }, update);
Or in the case where the index property was not there or irrelevant since the array is already ordered:
var imagesOrder = [
{ index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
];
for ( let [index, { url }] of Object.entries(imagesOrder) ) {
update.$set['images.'+index] = url;
}
/*
* Creates:
*
* { "$set": {
* "images.0": "/one",
* "images.1": "/two",
* "images.2": "/three"
* }}
*/
let response = await Users.updateOne({ id }, update);
So it's all pretty much the same thing. Note the common form of notation is actually a "string" for the key which includes the index position numerically. This is described in Dot Notation within the core documentation for the MongoDB query language.
The one main difference here is that should your new array contain more entries than the actual array stored in the document to be modified, that second form using the "dot notation" to the indexed position is going to fail since it cannot "set" an index position which does not exist.
For this reason even though there are other pitfalls to "replacing" the array as the original examples show, it's a lot safer than attempting to update via the positional index in the stored document.
Note that this should be enough to have you at least started in the right direction. Making this work with multiple users possibly updating the data at once can become pretty complicated in terms of update statements for both checking and merging changes.
In most cases the simple "replace" will be more than adequate at least for a while. And of course the main lesson here should be to not loop "async" methods in places where it is completely unnecessary. Most of the time what you really want to "loop" is the construction of the statement, if of course any looping is required at all and most of the time it really isn't.
Addendum
Just in case you or anyone had it in mind to actually store an array of objects with the index position values stored within them, this can become a little more complex, but it can also serve as an example of how to actually issue an update statement which does not "replace" the array and actually is safe considering it does not rely on indexed positions of the array but instead using matching conditions.
This is possible with the positional filtered $[<identifier>] syntax introduced in MongoDB 3.6. This allows conditions to specify which element to update ( i.e by matching url ) instead of including the index positions within the statement directly. It's safer since if no matching element is found, then the syntax allows for not attempting to change anything at all.
Also as demonstration the method to $sort the elements based on updated index values is shown. Noting this actually uses the $push modifier even though in this statement we are not actually adding anything to the array. Just reordering the elements. But it's how you actually do that atomically:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/longorder';
const opts = { useNewUrlParser: true };
// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
// schema defs
const imageSchema = new Schema({
index: Number,
url: String
})
const userSchema = new Schema({
images: [imageSchema]
});
const User = mongoose.model('User', userSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
);
// Create data
let _id = new ObjectId();
let user = await User.findOneAndUpdate(
{ _id },
{
'$push': {
'images': {
'$each': [
{ index: 2, url: '/one' },
{ index: 0, url: '/three' },
{ index: 1, url: '/two' }
],
'$sort': { 'index': 1 }
}
}
},
{ 'new': true, 'upsert': true }
);
log(user);
// Change order request
let orderImages = [
{ index: 2, url: '/three' },
{ index: 0, url: '/one' },
{ index: 1, url: '/two' }
];
let $set = { };
let arrayFilters = [];
for ( { index, url } of orderImages ) {
let key = url.replace(/^\//,'');
arrayFilters.push({ [`${key}.url`]: url });
$set[`images.$[${key}].index`] = index;
}
let ops = [
// Update the index value of each matching item
{ 'updateOne': {
'filter': { _id },
'update': { $set },
arrayFilters
}},
// Re-sort the array by index value
{ 'updateOne': {
'filter': { _id },
'update': {
'$push': {
'images': { '$each': [], '$sort': { 'index': 1 } }
}
}
}}
];
log(ops);
let response = await User.bulkWrite(ops);
log(response);
let newuser = await User.findOne({ _id });
log(newuser);
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
And the output, showing initial document state, the update and actual changes made:
Mongoose: users.deleteMany({}, {})
Mongoose: users.findOneAndUpdate({ _id: ObjectId("5bf6116621293f2ab3dec3d3") }, { '$setOnInsert': { __v: 0 }, '$push': { images: { '$each': [ { _id: ObjectId("5bf6116621293f2ab3dec3d6"), index: 2, url: '/one' }, { _id: ObjectId("5bf6116621293f2ab3dec3d5"), index: 0, url: '/three' }, { _id: ObjectId("5bf6116621293f2ab3dec3d4"), index: 1, url: '/two' } ], '$sort': { index: 1 } } } }, { upsert: true, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf6116621293f2ab3dec3d3",
"__v": 0,
"images": [
{
"_id": "5bf6116621293f2ab3dec3d5",
"index": 0,
"url": "/three"
},
{
"_id": "5bf6116621293f2ab3dec3d4",
"index": 1,
"url": "/two"
},
{
"_id": "5bf6116621293f2ab3dec3d6",
"index": 2,
"url": "/one"
}
]
}
[
{
"updateOne": {
"filter": {
"_id": "5bf6116621293f2ab3dec3d3"
},
"update": {
"$set": {
"images.$[three].index": 2,
"images.$[one].index": 0,
"images.$[two].index": 1
}
},
"arrayFilters": [
{
"three.url": "/three"
},
{
"one.url": "/one"
},
{
"two.url": "/two"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": "5bf6116621293f2ab3dec3d3"
},
"update": {
"$push": {
"images": {
"$each": [],
"$sort": {
"index": 1
}
}
}
}
}
}
]
Mongoose: users.bulkWrite([ { updateOne: { filter: { _id: 5bf6116621293f2ab3dec3d3 }, update: { '$set': { 'images.$[three].index': 2, 'images.$[one].index': 0, 'images.$[two].index': 1 } }, arrayFilters: [ { 'three.url': '/three' }, { 'one.url': '/one' }, { 'two.url': '/two' } ] } }, { updateOne: { filter: { _id: 5bf6116621293f2ab3dec3d3 }, update: { '$push': { images: { '$each': [], '$sort': { index: 1 } } } } } } ], {})
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 2,
"nModified": 2,
"nRemoved": 0,
"upserted": [],
"lastOp": {
"ts": "6626503031506599940",
"t": 139
}
}
Mongoose: users.findOne({ _id: ObjectId("5bf6116621293f2ab3dec3d3") }, { projection: {} })
{
"_id": "5bf6116621293f2ab3dec3d3",
"__v": 0,
"images": [
{
"_id": "5bf6116621293f2ab3dec3d6",
"index": 0,
"url": "/one"
},
{
"_id": "5bf6116621293f2ab3dec3d4",
"index": 1,
"url": "/two"
},
{
"_id": "5bf6116621293f2ab3dec3d5",
"index": 2,
"url": "/three"
}
]
}

Prevent Assigned by Reference

I am attempting to clone a few objects in an array based on some properties.
Given an array of objects:
[
{
id: 1,
data: {
user:
{
user1: 0,
user2: 1,
}
}
},
{
id: 2,
data: {
user:
{
user1: 0,
}
}
},
]
I want to transform the above into:
[
{
id: 1,
data: {
user: 'user1',
user_status: 0
}
},
{
id: 1,
data: {
user: 'user2',
user_status: 1,
}
},
{
id: 2,
data: {
user: 'user1',
user_status: 0,
}
},
]
Every user object within the array should have its user property transformed regardless of how many properties are in the user object. There's other properties in the data object that I want to copy but do not wish to modify.
The closest I got was:
result.rows.forEach((item, index) => {
for ( const user in item.data.user ) {
const notif = Object.assign({}, item);
notif.data.user = user;
notif.data.user_status = item.data.user[user];
result.rows.push(notif);
}
});
However, the above acts as if notif it is assigned by reference(?) and is mutating the original object. Using a console.log during the for in loop results in:
console.log(notif.id, notif.data.user, notif.data.user_status)
// Results in 1, user1, undefined
console.log(item.data.user, item.data.user[user])
// Results in user1, undefined instead of the expect { 'user1': 0 }
This results in an array like:
{
id: 1,
data: {
user: 'user2', // Should be user1
user_status: undefined, // Should be 0
}
},
{
id: 1,
data: {
user: 'user2', // Should be user2 -- hooray but in a bad way
user_status: undefined, // Should be 1
}
}
All of this is running on a Node.js (8.11.1) server.
The data in your item references the original data object, because Object.assign gives a shallow clone, not a deep clone.
This would probably be achieved most elegantly by reduce-ing into an array in one go, extracting all the primitives immediately, rather than trying to work with the (by-reference) objects:
const input=[{id:1,data:{user:{user1:0,user2:1,}}},{id:2,data:{user:{user1:0,}}},]
const output = input.reduce((a, { id, data: { user: users }}) => {
Object.entries(users).forEach(([user, user_status]) => {
a.push({ id, data: { user, user_status }});
});
return a;
}, []);
console.log(output);
A fix to your original code would involve cloning the data property as well:
const input=[{id:1,data:{user:{user1:0,user2:1,}}},{id:2,data:{user:{user1:0,}}},]
const output = [];
input.forEach((item, index) => {
for (const user in item.data.user) {
const notif = Object.assign({}, item);
notif.data = Object.assign({}, item.data);
notif.data.user = user;
notif.data.user_status = item.data.user[user];
output.push(notif);
}
});
console.log(output);
You can achieve this using two .forEach loops. You can't loop with a .forEach inside an object, instead, we will loop over Object.keys of the object. Which is essentially an array of keys.
Then for each fragment, for example
{
id: 1,
data: {
user: 'user1',
user_status: 0
}
}
we can push a brand new object to res.
Here is the code:
let res = [];
data.forEach((e, i, arr) => Object.keys(e.data.user).forEach((k, j) => {
res.push({
id: e.id,
data: {
user: k,
user_status: j
}
});
}));
console.log(res);
<script>
const data=[{id:1,data:{user:{user1:0,user2:1,}}},{id:2,data:{user:{user1:0,}}}];
</script>
One way would be to transform each object in the array using map into a subarray of new objects, then flatten the result using reduce.
const input = [
{
id: 1,
data: {
user:
{
user1: 0,
user2: 1,
}
}
},
{
id: 2,
data: {
user:
{
user1: 0,
}
}
},
];
const output = input.map(obj => Object.keys(obj.data.user)
.map(user => ({
id: obj.id,
data: {
user, user_status: obj.data.user[user]
}
})
))
.reduce((a, b) => a.concat(b));
console.log(output)

How to print the values of a array which is in another array insted of "[object]"

So I have an array of "sessions":
var sessions = [];
function Session(title, speaker) {
this.title = title;
this.speaker = speaker;
this.id = nextId;
this.ratings = [];
this.currentRatingId = 0;
this.currentSessionState = SessionState.Created; //SessionState is an enum
}
And the session has an array of ratings:
function Rating(evaluator,ratingValue) {
this.ratingId;
this.ratingValue = ratingValue;
this.evaluator = evaluator;
this.evaluatedSession;
}
If I print my "sessions" array I get this for example:
[Session {
title: 'Javasession',
speaker: 'JavaSpeaker ',
id: 1,
ratings: [[Object], [Object]],
currentRatingId: 2,
currentSessionState: 2
},
Session {
title: 'C#Session',
speaker: 'C#Speaker',
id: 2,
ratings: [[Object]],
currentRatingId: 1,
currentSessionState: 2
}]
As you can see the array which is in the array "session" doesn't print the objects. It prints only "[object]"..
Is it somehow possible to print the values of each array which is in another arrays without using any loops (for,foreach).... ?
You can use JSON#stringify method to print it as a string.
Example:
const arr = [
{
subArr: [
{
subSubArr:[
{
a:1
}
]
}
]
}
];
const res = JSON.stringify(arr);
console.log(res);
You can use console.dir() in browser.
const arr = [
{
subArr: [
{
subSubArr:[
{
a:1
}
]
}
]
}
];
console.dir(arr, { depth: null });
You can use console.dir with { depth: null }
using a simplified array:
let sessions = [ Session: { ratings: [ {Rating: 1}, {Rating: 2} ] } ];
Running console.log(sessions); results in:
[ { Session: { ratings: [Object] } } ]
Running console.dir(sessions, { depth: null}) results in:
[ { Session: { ratings: [ { Rating: 1 }, { Rating: 2 } ] } } ]

Output is 'undefined' on retrieval of array field from Mongodb using node.js

I want to fetch an array which consists of id referenced from a different collection.
Region
var list1 = {
region_id: 'us-east-1',
region_name:'US East(N.Virginia)',
os_type: 'linux',
inst_name: {$ref : "instance", $id: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38]},
prov_id: 'aws'
};
Instance
{
inst_id: 1,
inst_type: 't2.micro',
vcpu: 1,
memory_gib: 1,
prov_id: 'aws'
};
{
inst_id: 2,
inst_type: 't2.small',
vcpu: 1,
memory_gib: 2,
prov_id: 'aws'
};
My node.js code
region.findOne(({
$and: [{
"region_id": "ap-east-1"
}, {
"os_type": "sles"
}]
}),
function (err, result) {
if (err) {
throw (err);
} else {
var arr = result.inst_name;
var arrli = arr.$id;
console.log(arrli);
}
});
The output for this is printed as 'undefined' in console. How do I fetch the array from $id field as it is?

Categories