MongoDB using parameter for nested location - javascript

have a little newbe problem although I couldn't find a solution for similar problems, that worked for me.
Here is my collection:
{
"_id": ObjectId("5bc712851224ceec702d9bdf"),
"index": "123456",
"name": "Jan",
"surname": "Nowak",
"grades": {
"IABD": [
2,
3.5,
4
],
"NPAD": [
4,
4,
5
]
}
}
now I need to push additional grades to specific (function parameters) courses.
So I tried tackling it on a few levels and I'd love somebody to walk me through it according to this:
First I wanted to succeed not passing course as a parameter:
function add_grade(index="123456", course="IABD", grade=5.5)
{
db.students.update( {"index" : index }, { $push: { "grades" : { "IABD" : grade } } } );
}
well nothing happened (grade was not added to the list of grades)
I wanted to see some result, so I wanted to see if $set would work and it did!
function add_grade(index="123456", course="IABD", grade=5.5)
{
db.students.update( {"index" : index }, { $set: { "grades" : { "IABD" : grade } } } );
}
but it threw away my entire grades object (as expected). At least I know I'm on the right track.
Question 1: Why $push didn't work the way I expected
Question 2: how to use course parameter in set/push?
Just to clarify Q2, I'm not lazy, I've tried many approaches, none of which worked, please help!

You can try below query. That push 6 into IABD
db.getCollection('students').update( { "index": "123456" }, { $push: { "grades.IABD" : 6 } });

$push is not working the way you are expecting because the array field is in an embedded document and to push you need to use
dot notation i.e. instead of
{ "$push": { "grades" : { "IABD" : grade } } }
what you need is to specify the field as dot notation
{ "$push": { "grades.IABD" : grade } }
To use the course parameter in push you would want to create an update object that holds the dot notation
{ "grades.<course>" : grade }
for example
var course = "IABD";
var grade = 5.5;
var update = {};
update["grades." + course] = grade;
printjson(update) // prints { "grades.IABD" : 5.5 }
So your function will look like
function add_grade(index="123456", course="IABD", grade=5.5) {
var update = {};
update["grades." + course] = grade;
db.students.update(
{ "index" : index },
{ "$push": update }
);
}

Related

Filter Array Using Partial String Match in Javascript

I have an array of objects where the value I need to filter on is buried in a long string. Array looks like:
{
"data": {
"value": "{\"cols\":[\"parent_sku\"],\"label\":\"Style\",\"description\":\"Enter Style.\",\"placeholderText\":\"Style 10110120103\"}",
"partnerId": 1
}
},
So if I wanted to grab all the partnerId objects where value includes parent_sku how would I do that?
console.log(data.value.includes('parent_sku') returns cannot read property 'includes' of null.
EDIT:
Didn't think this mattered, but judging by responses, seems it does. Here's the full response object:
Response body: {
"data": {
"configurationByCode": [
{
"data": {
"value": "{\"cols\":[\"parent_sku\"],\"label\":\"Style\",\"description\":\"Enter Style.\",\"placeholderText\":\"Style 10110120103\"}",
"partnerId": 1
}
}
I'm passing that into a re-usable function for filtering arrays:
const parentSkuPartners = filterArray(res.body.data.configurationByCode, 'parent_sku');
Function:
function filterArray(array, filterList) {
const newList = [];
for (let i = 0; i < array.length; i += 1) {
console.log('LOG', array[i].data.value.includes('parent_sku');
}
}
The problem is somewhere else. The code you've tried should work to find if a value contains a string – I've added it the snippet below and you'll see it works.
The issue is how you are accessing data and data.value. The error message clearly states that it believes that data.value is null. We would need to see the code around it to be able to figure out what the problem is. Try just logging to console the value of data before you run the includes function.
const data = {
"value": "{\"cols\":[\"parent_sku\"],\"label\":\"Style\",\"description\":\"Enter Style.\",\"placeholderText\":\"Style 10110120103\"}", "partnerId": 1
};
console.log('includes?', data.value.includes('parent_sku'));
You can use data.value.includes('parent_sku') as you have suggested. The issue here is that your object is nested inside an unnamed object.
try:
"data": {
"value": "{\"cols\":[\"parent_sku\"],\"label\":\"Style\",\"description\":\"Enter Style.\",\"placeholderText\":\"Style 10110120103\"}",
"partnerId": 1
}
The problem was some of the values for value were null. Adding an extra conditional fixed it:
if (array[i].data.value !== null) {
Use lodash includes, and lodash filter like
let configurationByCode = [{
data: {
value: {
cols:["parent_sku"],
label:"Style",
description:"Enter Style.",
placeholderText:"Style 10110120103"
},
"partnerId": 1
}
}, {
data: {
value: {
cols:["nothing"],
label:"Style",
description:"Enter Style.",
placeholderText:"Style 10110120103"
},
"partnerId": 2
}
}];
let wantedData = _.filter(configurationByCode, (config) => {
return _.includes(config.data.value.cols, 'parent_sku');
});
console.log( wantedData );
https://jsfiddle.net/76cndsp2/

mongoose convert string collection to boolean [duplicate]

In MongoDB, is it possible to update the value of a field using the value from another field? The equivalent SQL would be something like:
UPDATE Person SET Name = FirstName + ' ' + LastName
And the MongoDB pseudo-code would be:
db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );
The best way to do this is in version 4.2+ which allows using the aggregation pipeline in the update document and the updateOne, updateMany, or update(deprecated in most if not all languages drivers) collection methods.
MongoDB 4.2+
Version 4.2 also introduced the $set pipeline stage operator, which is an alias for $addFields. I will use $set here as it maps with what we are trying to achieve.
db.collection.<update method>(
{},
[
{"$set": {"name": { "$concat": ["$firstName", " ", "$lastName"]}}}
]
)
Note that square brackets in the second argument to the method specify an aggregation pipeline instead of a plain update document because using a simple document will not work correctly.
MongoDB 3.4+
In 3.4+, you can use $addFields and the $out aggregation pipeline operators.
db.collection.aggregate(
[
{ "$addFields": {
"name": { "$concat": [ "$firstName", " ", "$lastName" ] }
}},
{ "$out": <output collection name> }
]
)
Note that this does not update your collection but instead replaces the existing collection or creates a new one. Also, for update operations that require "typecasting", you will need client-side processing, and depending on the operation, you may need to use the find() method instead of the .aggreate() method.
MongoDB 3.2 and 3.0
The way we do this is by $projecting our documents and using the $concat string aggregation operator to return the concatenated string.
You then iterate the cursor and use the $set update operator to add the new field to your documents using bulk operations for maximum efficiency.
Aggregation query:
var cursor = db.collection.aggregate([
{ "$project": {
"name": { "$concat": [ "$firstName", " ", "$lastName" ] }
}}
])
MongoDB 3.2 or newer
You need to use the bulkWrite method.
var requests = [];
cursor.forEach(document => {
requests.push( {
'updateOne': {
'filter': { '_id': document._id },
'update': { '$set': { 'name': document.name } }
}
});
if (requests.length === 500) {
//Execute per 500 operations and re-init
db.collection.bulkWrite(requests);
requests = [];
}
});
if(requests.length > 0) {
db.collection.bulkWrite(requests);
}
MongoDB 2.6 and 3.0
From this version, you need to use the now deprecated Bulk API and its associated methods.
var bulk = db.collection.initializeUnorderedBulkOp();
var count = 0;
cursor.snapshot().forEach(function(document) {
bulk.find({ '_id': document._id }).updateOne( {
'$set': { 'name': document.name }
});
count++;
if(count%500 === 0) {
// Excecute per 500 operations and re-init
bulk.execute();
bulk = db.collection.initializeUnorderedBulkOp();
}
})
// clean up queues
if(count > 0) {
bulk.execute();
}
MongoDB 2.4
cursor["result"].forEach(function(document) {
db.collection.update(
{ "_id": document._id },
{ "$set": { "name": document.name } }
);
})
You should iterate through. For your specific case:
db.person.find().snapshot().forEach(
function (elem) {
db.person.update(
{
_id: elem._id
},
{
$set: {
name: elem.firstname + ' ' + elem.lastname
}
}
);
}
);
Apparently there is a way to do this efficiently since MongoDB 3.4, see styvane's answer.
Obsolete answer below
You cannot refer to the document itself in an update (yet). You'll need to iterate through the documents and update each document using a function. See this answer for an example, or this one for server-side eval().
For a database with high activity, you may run into issues where your updates affect actively changing records and for this reason I recommend using snapshot()
db.person.find().snapshot().forEach( function (hombre) {
hombre.name = hombre.firstName + ' ' + hombre.lastName;
db.person.save(hombre);
});
http://docs.mongodb.org/manual/reference/method/cursor.snapshot/
Starting Mongo 4.2, db.collection.update() can accept an aggregation pipeline, finally allowing the update/creation of a field based on another field:
// { firstName: "Hello", lastName: "World" }
db.collection.updateMany(
{},
[{ $set: { name: { $concat: [ "$firstName", " ", "$lastName" ] } } }]
)
// { "firstName" : "Hello", "lastName" : "World", "name" : "Hello World" }
The first part {} is the match query, filtering which documents to update (in our case all documents).
The second part [{ $set: { name: { ... } }] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline). $set is a new aggregation operator and an alias of $addFields.
Regarding this answer, the snapshot function is deprecated in version 3.6, according to this update. So, on version 3.6 and above, it is possible to perform the operation this way:
db.person.find().forEach(
function (elem) {
db.person.update(
{
_id: elem._id
},
{
$set: {
name: elem.firstname + ' ' + elem.lastname
}
}
);
}
);
I tried the above solution but I found it unsuitable for large amounts of data. I then discovered the stream feature:
MongoClient.connect("...", function(err, db){
var c = db.collection('yourCollection');
var s = c.find({/* your query */}).stream();
s.on('data', function(doc){
c.update({_id: doc._id}, {$set: {name : doc.firstName + ' ' + doc.lastName}}, function(err, result) { /* result == true? */} }
});
s.on('end', function(){
// stream can end before all your updates do if you have a lot
})
})
update() method takes aggregation pipeline as parameter like
db.collection_name.update(
{
// Query
},
[
// Aggregation pipeline
{ "$set": { "id": "$_id" } }
],
{
// Options
"multi": true // false when a single doc has to be updated
}
)
The field can be set or unset with existing values using the aggregation pipeline.
Note: use $ with field name to specify the field which has to be read.
Here's what we came up with for copying one field to another for ~150_000 records. It took about 6 minutes, but is still significantly less resource intensive than it would have been to instantiate and iterate over the same number of ruby objects.
js_query = %({
$or : [
{
'settings.mobile_notifications' : { $exists : false },
'settings.mobile_admin_notifications' : { $exists : false }
}
]
})
js_for_each = %(function(user) {
if (!user.settings.hasOwnProperty('mobile_notifications')) {
user.settings.mobile_notifications = user.settings.email_notifications;
}
if (!user.settings.hasOwnProperty('mobile_admin_notifications')) {
user.settings.mobile_admin_notifications = user.settings.email_admin_notifications;
}
db.users.save(user);
})
js = "db.users.find(#{js_query}).forEach(#{js_for_each});"
Mongoid::Sessions.default.command('$eval' => js)
With MongoDB version 4.2+, updates are more flexible as it allows the use of aggregation pipeline in its update, updateOne and updateMany. You can now transform your documents using the aggregation operators then update without the need to explicity state the $set command (instead we use $replaceRoot: {newRoot: "$$ROOT"})
Here we use the aggregate query to extract the timestamp from MongoDB's ObjectID "_id" field and update the documents (I am not an expert in SQL but I think SQL does not provide any auto generated ObjectID that has timestamp to it, you would have to automatically create that date)
var collection = "person"
agg_query = [
{
"$addFields" : {
"_last_updated" : {
"$toDate" : "$_id"
}
}
},
{
$replaceRoot: {
newRoot: "$$ROOT"
}
}
]
db.getCollection(collection).updateMany({}, agg_query, {upsert: true})
(I would have posted this as a comment, but couldn't)
For anyone who lands here trying to update one field using another in the document with the c# driver...
I could not figure out how to use any of the UpdateXXX methods and their associated overloads since they take an UpdateDefinition as an argument.
// we want to set Prop1 to Prop2
class Foo { public string Prop1 { get; set; } public string Prop2 { get; set;} }
void Test()
{
var update = new UpdateDefinitionBuilder<Foo>();
update.Set(x => x.Prop1, <new value; no way to get a hold of the object that I can find>)
}
As a workaround, I found that you can use the RunCommand method on an IMongoDatabase (https://docs.mongodb.com/manual/reference/command/update/#dbcmd.update).
var command = new BsonDocument
{
{ "update", "CollectionToUpdate" },
{ "updates", new BsonArray
{
new BsonDocument
{
// Any filter; here the check is if Prop1 does not exist
{ "q", new BsonDocument{ ["Prop1"] = new BsonDocument("$exists", false) }},
// set it to the value of Prop2
{ "u", new BsonArray { new BsonDocument { ["$set"] = new BsonDocument("Prop1", "$Prop2") }}},
{ "multi", true }
}
}
}
};
database.RunCommand<BsonDocument>(command);
MongoDB 4.2+ Golang
result, err := collection.UpdateMany(ctx, bson.M{},
mongo.Pipeline{
bson.D{{"$set",
bson.M{"name": bson.M{"$concat": []string{"$lastName", " ", "$firstName"}}}
}},
)

MongoDB find where $value exists and timestamp $gte JS

I'm trying to search for the existence of a nested element, as well as getting a timestamp greater than:
db.stats.find( { $and: [ { 'data.Statistics': {$exists: true} },{ timestamp: {$gte: 1} } ] }
From the docs I can't see where I'm going wrong, but I'm not getting anything back.
Just doing:
var query = {};
query["data.Statistics"] = {$exists: true}
works however.
The $and operator is not really necessary in this case as it can be implicitly used by just specifying a comma separated list of expressions thus you can re-write your query as:
db.stats.find({
"data.Statistics": { "$exists": true },
"timestamp": { "$gte": 1 }
});
If using a variable to create the query object using the square-bracket notation, you can approach it like
var query = {};
query["data.Statistics"] = { "$exists": true };
query["timestamp"] = { "$gte": 1 };
db.stats.find(query);

Restructure JS object to fit binding MVC model

I have a javascript object with nested objects obtained by a plugin saved in a variable called 'json':
// var json:
Object {0: Object, 1: Object}
0: country
countryCapital: Object
ministers: Array[1]
1: country
countryCapital: Object
ministers: Array[3]
// Should be restructured to fit MVC models
World
Country Array [2]
Capital: Object
Minister: Array [1]
Because I'm sending the data using jQuery-ajax to a MVC controller I'm trying to rename/restructure them to bind them to a MVC Model so I can send the whole 'World' object as 1 parameter.
// Controller
[HttpPost]
public void Save(World world)
// Model structure:
World
* Country (list)
1 Capital
* Minister (list)
How do I convert the javascript object to the right structure that fits the Model parameter? Do I need to loop everything or can it be done in a better way?
UPDATE:
json object update, it's a lot of data so I simplified it.
// World
{
"0": { // Country
"ministers":
{
"objects": [
{
"name": "name1"
},
{
"name": "name2"
}
]
},
"countryCapital": {
"name": "...",
}
},
"1": { // Country
"ministers":
{
"objects": [
{
"name": "name1"
},
{
"name": "name2"
}
]
},
"countryCapital": {
"name": "...",
}
}
}
I suppose you're searching for $.map().
At the background it's looping your collection but it much cleaner to use it in your case.
Updated to use your example json. Jsfiddle
var world = {}
for (var countryIteration in originalObject) {
var country = originalObject[countryIteration];
world[countryIteration] = { //you need to this because you need dynamic keys.
capital : country.countryCapital.name,
ministers : country.ministers.objects
}
}
I found a working solution thanks to teo van kot using jQuery map.
var world = {}
$.map(json, function (value, i) {
if (i == "countryCapital") {
world.Capital = value
}
if (i == "ministers") {
world.Minister= value
$.each(value, function (j, minister) {
world.Minister[j].Name = minister.name;
delete minister.name;
});
}
}, world);

Underscorejs filter on collection of object 2 levels deep

I have a an array of objects, that contains and array of objects, that contains an array of objects.
What I want to do is to filter on a property in the last array of objects.
Here is an example dataset
var sampleData=[
{
"name":"Cooking",
"shelve":[
{
"name":"Shelve 1",
"drawers":[
{
"Name":"Left"
},
{
"Name":"Middle"
}
]
},
{
"name":"Shelve 2",
"drawers":[
{
"Name":"Middle side"
},
{
"Name":"Left"
},
{
"Name":"Up"
}
]
}
]
},
{
"name":"DBs",
"shelves":[
{
"name":"Shelve 3",
"drawers":[
{
"Name":"asdfasdf"
},
{
"Name":"New Test 12"
}
]
}
]
}
];
Basically what I would like to do is to be able to get back the objects where drawer.name contains a substring. I would like for it to return the full object graph so if I searched for Middle I would expect to get back
var sampleData=[
{
"name":"Cooking",
"shelve":[
{
"name":"Shelve 1",
"drawers":[
{
"Name":"Middle"
}
]
},
{
"name":"Shelve 2",
"drawers":[
{
"Name":"Middle side"
}
]
}
]
}
];
I was hoping that using underscore would help and I initially just tried to nest filters but that did not work.
I tried the following and while found would evaluate to true when it should it would not filter as I hoped.
var secs = _.filter(sampleData, function(section) {
_.filter(section.shelve, function(shelve) {
_.filter(shelve.drawers, function(drawer) {
var found = drawer.Name.indexOf('Middle') !== -1;
if(found) {
var xa = 'found it!!';
}
return drawer.Name.indexOf('Middle') !== -1;
});
});
});
Here is a quick jsfiddler example. http://jsfiddle.net/cnalk/GtfNj/3/
Unfortunately, a basic array filter like _.filter doesn't provide a way to do two-step filtering (first, by values of an array; then by how many values passed), so you need to write your own filter function for each layer, such as:
function filter_shelf( shelf ) {
var filtered = _.omit( shelf, 'drawers' );
filtered.drawers = _.filter( shelf.drawers, filter_drawer );
return filtered.drawers.length && filtered;
}
Complete fiddle: http://jsfiddle.net/GtfNj/5/
Each step considers the property it needs to filter, filters it by the step "below" it, then returns a filtered copy of itself or false if the property ended up empty from the filter. The step "above" it then filters based on that result. And so on...
Ps. your sample data uses shelve or shelves inconsistently.

Categories

Resources