Pushing to nested array inside specific element of parent array (MongoDB) - arrays

I'm rethinking how I want to structure some data which is currently being stored in the Users Collection. Previously, my server would receive messages, find the user document with the right profile.website field, and push an item into the profile.siteMessages array:
{
"_id": "hr9ck5Fis5YuvqCqP",
"profile": {
"website": "localhost",
"siteMessages": [
{
"text": "sddsd",
"createdAt": 1482001227204
},
]
}
}
Id like to change the structure to look something like the following. Instead of storing all messages, of which multiple messages could come from the same user, in a top level array in profile, I would have a profile.siteVisitors field which contains a visitorId and then the array of messages:
{
"_id": "dgfsdfdfsdf",
"emails": [
{
"address": "user2#test.com",
"verified": false
}
],
"profile": {
"website": "localhost",
"siteVisitors:" [
{
"visitorId": "74585242",
"messages": [
{
"text": "A string",
"createdAt": 1482001260853
},
{
"text": "Another string",
"createdAt": 1482001260854
}
]
},
{
"visitorId": "76672242",
"messages": [
{
"text": "A string",
"createdAt": 1482001260855
}
]
}
]
}
}
Keeping with the structure shown above, how would I query for and update the profile.siteVisitiors.messages array? Currently I query and update the Collection using something like the following:
Meteor.users.update(
{'profile.website': url},
{$push: {'profile.siteMessages': msgItem}},
function(err) {
if (err) {
console.log('whoops!' + err)
} else {
//success
}
});
How would I update the newly structured messages array? I would need to match a User documents profile.website field, match a visitorId in the profile.siteVisitors array, and then push a new element into the messages array, but I'm not sure how this would look as a MongoDB query.
EDIT I've hacked together the following which seems to work, but is very ugly. How can I improve this?
Meteor.users.update(
{"profile.website" : "localhost" , "profile.siteVisitors" : {$elemMatch: {"visitorId" : data.chirpVisitorId} } },
{$push: { "profile.siteVisitors.$.messages": {"text" : data.msg, "createdAt" : data.msg.createdAt} } },
function(err, res) {
if (err) {
console.log('failed to push ' + err)
} else {
console.log('success on new push ' + res)
if (res < 1) {
let item = {
"visitorId": data.chirpVisitorId,
"messages": [data.msg]
}
Meteor.users.update(
{"profile.website": "localhost"},
{$push: {'profile.siteVisitors': item}},
function(err, res) {
if (err) {
console.log(err)
} else {
console.log(res + " updated")
}
}
)
}
}
})

You can use the $(update) operator.
Try the following query:
db.collection.update(
{"profile.website" : "localhost" , "profile.siteVisitors" : {$elemMatch: {"visitorId" :'76672242'} } },
{ $push: { "profile.siteVisitors.$.messages": {"text" : <newText>, "createdAt" : <newCreatedDate>} } }
)
Hope this helps.

Related

Push object into array if exists otherwise set object in MongoDB

This is the document I currently have:
{
"_id": "",
"title": "My Watchlist",
"series": [{
"seriesId": 1,
"following": true,
"seasons": []
}, {
"seriesId": 1,
"following": false,
"seasons": []
}]
}
As you can see there are currently 2 objects with the seriesId 1, but with a different following boolean.
If the query matches with _id it should push the new object into series, if within the "series"-array an object with the same "seriesId" already exists it should change the fields within that object, instead of adding a new object.
I currently have the following query:
users.update(
{"_id": req.body.userId},
{
"$push": {
"series": {"seriesId": req.body.seriesId, "following": req.body.following}
}
}, (err, data) => {
if (err)
next(err);
});
If I use $set it does not add the object if it didn't originaly exist yet, and as far as I know you cannot both use $push and $set?
Can this be fixed in any way or do I have to rethink my Schema?
You can use two update query :
if _id is found and seriesId is not in the array, add the new item to the array :
db.series.update({
"_id": req.body.userId,
"series": {
"$not": {
"$elemMatch": {
"seriesId": req.body.seriesId
}
}
}
}, {
$addToSet: {
series: {
"seriesId": req.body.seriesId,
"following": req.body.following,
"seasons": []
}
}
}, { multi: true });
if _id is found and seriesId is found in the array, update the array item :
db.series.update({
"_id": req.body.userId,
"series.seriesId": req.body.seriesId
}, {
$set: {
"series.$.following": req.body.following
}
}, { multi: true });

How to merge into array?

Here is my array from db,
[
{
"_id": "58144e6c0c8d7534f4307269",
"doctor_id": "5813221ace684e2b3f5f0a6d",
"prescription": [
{
"_id": "58144e6c0c8d7534f430726a",
"medicine_id": "10011241343"
}
]
I want to merge with only prescription like this
[
{
"_id": "58144e6c0c8d7534f4307269",
"doctor_id": "5813221ace684e2b3f5f0a6d",
"prescription": [
{
"_id": "58144e6c0c8d7534f430726a",
"medicine_id": "10011241343"
},
"prescription": [
{
"_id": "58144e6c0c8d7534f430726a", // it should be autogenerated
"medicine_id": "10011241344"
}
]
How can I do this?
I have tried like this
var arr = data.presription
arr=req.body// which contains only medicine id
and then by
dbModel.user.findById(data._id, function(err, data) {
data.prescription = arr;
data.save(function(err) {
if (err) {
res.status(202).json({
"success": "0",
});
} else {
res.status(200).json({
"success": "1"
});
}
})
});
But it is saving the same. How can I do this?
Note: Even when I do console.log(arr) only the old data is printing.
So data.presription is an array from the start.
data.presription.push( {
"_id": "58144e6c0c8d7534f430726a",
"medicine_id": "10011241344"
});
This is NOT a merge, what you want to do is to append (or push) items into an existing array.

updating a JSON array in AWS dynamoDB

My document looks like this:
{
"data": {
"eventId": "20161029125458-df-d",
"name": "first",
"purpose": "test",
"location": "yokohama",
"dateArray": [],
"attendees": [
{
"attendeeId": "2016102973634-df",
"attendeeName": "lakshman",
"personalizedDateSelection": {}
},
{
"attendeeId": "2016102973634-tyyu",
"attendeeName": "diwaakar",
"personalizedDateSelection": {}
}
]
}
}
Say, I need to update the attendee JSON array with attendeeId: 2016102973634-df. I tried many ways ways using update and condition expression, but no success.
Here is my try:
const params = {
TableName: "event",
Key: {
"eventId": eventId
},
UpdateExpression: "SET attendees[???] = ",
ConditionExpression: attendees.attendeeId = "2016102973634-df",
ExpressionAttributeValues: {
":attendee" : attendeeList
},
ReturnValues: "ALL_NEW"
};
dynamo.update(params, (err, data) => {
if (err) {
return reject(err);
}
console.log(data.Attributes);
});
Could not find any resources for updating an Json in a array.
After #notionquest's comment:
- Have not used any JsonMarshaller. Initially I added the empty array to attendees field like this:
{
"eventId": "20161029125458-df-d",
"name": "first",
"purpose": "test",
"location": "yokohama",
"dateArray": [],
"attendees": []
}
and then When a new attendee comes I add it to the attendees property like this:
const attendee = {
"attendeeName": "user1",
"personalizedDateSelection": {"today": "free"}
}
const attendeeList = [attendee];
const eventId = "20161029125458-df-d";
const params = {
TableName: "event",
Key: {
"eventId": eventId
},
UpdateExpression: "SET attendees = list_append(attendees, :attendee)",
ExpressionAttributeValues: {
":attendee" : attendeeList
},
ReturnValues: "ALL_NEW"
};
dynamo.update(params, (err, data) => {
if (err) {
return reject(err);
}
console.log("in update dynamo");
console.log(data.Attributes);
});
As you have seen in the above snippets, initially I add empty [] array and add a new attendee using the above code. Now, How do I update a specific JSON in an array. If you say that is not possible, what else can I try?
Should I try this :
Get the Full JSON.
Manipulate the JSOn and change the things I want in my nodeJS.
And then update the new JSON to dynamoDB.
But this consumes two calls to dynamoDB which seems to be inefficient.
Would like to know If there is any round way ?
you can store the index of list. while updating the list we can use them. For example ,
{
"data": {
"eventId": "20161029125458-df-d",
"name": "first",
"purpose": "test",
"location": "yokohama",
"dateArray": [],
"attendees": [
{
"index":0,
"attendeeId": "2016102973634-df",
"attendeeName": "lakshman",
"personalizedDateSelection": {}
},
{
"index":1,
"attendeeId": "2016102973634-tyyu",
"attendeeName": "diwaakar",
"personalizedDateSelection": {}
}
]
}
}
const params = {
TableName: "event",
Key: {
"eventId": eventId
},
UpdateExpression: "SET attendees[attendee.index].attendeeName = :value",
ExpressionAttributeValues: {
":value" : {"S":"karthik"}
},
ReturnValues: "ALL_NEW"
};
dynamo.update(params, (err, data) => {
if (err) {
return reject(err);
}
console.log(data.Attributes);
});
An example of an update query:
Data structure (saved in DynamoDB)
{
tenant_id: 'tenant_1',
users: {
user1: {
_id: 'user1',
email_address: 'test_email_1#gmail.com'
},
user2: {
_id: 'user2',
email_address: 'test_email_2#gmail.com'
}
}
}
Data for update (used in the params)
var user = {
email_address: 'updated#gmail.com'
}
Params
var params = {
TableName: 'tenant-Master',
Key: {
"tenant_id": 'tenant_1'
},
UpdateExpression: "set #users.user1 = :value",
ExpressionAttributeNames: {
"#users": "users"
},
ExpressionAttributeValues: {
":value": user,
},
};
Explanation
By switching to a map of maps from an array of maps we can now use UpdateExpression: "set #users.user1 = :value" to update our nested object at the map of users with the id of user1.
NOTE: This method as is will REPLACE the entire map object at users.user1. Some changes will need to be made if you want to keep pre-existing data.
I could not find any answer to query and update the JSON-array. I think this may be AWS profitable motive to not allow those features. If you need to query on a particular ID other than primary key, you need to make a secondary index which is cost effective. This secondary index cost is additional to the dyn
amoDB table cost.
Since, I did not want to pay extra bucks on secondary index, I changed my dynamoDB schema to the following:
{
"data": {
"eventId": "20161029125458-df-d",
"name": "first",
"purpose": "test",
"location": "yokohama",
"dateArray": [],
"attendees": {
"2016102973634-df": {
"attendeeId": "2016102973634-df",
"attendeeName": "lakshman",
"personalizedDateSelection": {}
},
"2016102973777-df": {
"attendeeId": "2016102973777-df",
"attendeeName": "ffff",
"personalizedDateSelection": {}
}
}
}
}
Changing attendees from [] to {}. This allows me the flexibility to query particular attendeeId and change the entire JSON associated with that. Even though, this is a redundant step, I do not want to spend extra bucks on my hobby project.

Only one element returned in array

I am trying to find elements from my MongoDB database with meteor.
I managed to filter and go through the structure of my array, but the result is a single element, and not all the elements matching the criteria.
Query :
var json = Tests1VerlIR.find({}, {fields: {entries: {$elemMatch: {'payload.id': {$eq: this.params._id}} } } }).fetch();
this.response.setHeader('Content-Type', 'application/json');
this.response.end(JSON.stringify(json));
Data Structure :
{"entries":
[{"method":"POST",
"source":"ex",
"path":"/ex",
"time":1464615406900,
"payload":
{"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"},
"_id":"574c41ee578d01af3664cbaf"},
{"method":"POST",
"source":"ex",
"path":"/ex",
"time":1464615406900,
"payload":
{"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"},
"_id":"574c41ee578d01af3664cbaf"}, {...}]}
Response :
[
{
"_id":
{
"_str": "576155d7a605348159cd1f1a"
},
"entries":
[
{
"method": "POST",
"source": "ex",
"path": "/ex",
"time": 1464615406900,
"payload":
{
"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"
},
"_id": "574c41ee578d01af3664cbaf"
}
]
}
]
You cannot return multiple elements of an array matching your criteria in any form of a basic .find() query. To match more than one element you need to use the .aggregate() method instead.
refer this link.
Tests1VerlIR.aggregate([
{ "$match": { "entries.payload.id": "2" } },
// Unwind the array to denormalize
{ "$unwind": "$entries" },
// Match specific array elements
{ "$match": { "entries.payload.id": "2" } },
// Group back to array form
{ "$group": {
"_id": "$_id",
"entries": { "$push": "$entries" }
}}
])
Solution :
var json = Tests1VerlIR.aggregate({"$unwind": "$entries"}, {$match: {'entries.payload.id': this.params._id} });

MEAN-Stack MongoDB Sub Array Delete - Works in IDE, not in API

I have a [user] document stored that contains a nested sub-array [profiles],[favorites]. I am simply trying to delete($pull) a favorites from a given profile based on the favorites name.
{
"_id" : ObjectId("558d53eebdd9804820090fa1"),
"name" : "Frank",
"email" : "Frank#FrankTheTank.com",
"profiles" : [
{
"avatar" : "div-male",
"age" : "35",
"gender" : "Male",
"profilename" : "Oly Lifter",
"_id" : ObjectId("558d5404bdd9804820090fa2"),
"favorites" : [
{
"name" : "Power Clean"
},
{
"name" : "Hang Clean"
},
{
"name" : "Clean and Jerk"
}
],
"createdAt" : ISODate("2015-06-26T13:30:44.661Z")
}
],
"createdAt" : ISODate("2015-06-26T13:30:22.884Z"),
"role" : "user",
"__v" : 0
}
Using a MongoDB IDE robomongo, I'm able to successfully remove a favorite item from a known User and Profile ID using this
db.users.update($find: {
'profiles': {
'profiles._id': ObjectId("558d5404bdd9804820090fa2")
},
{
$pull: {
'profiles.$.favorites': {
'name': 'Hang Clean'
}
}
})
However, when I call from my server API using the following syntax, I receive an error, note req.body._id = "558d5404bdd9804820090fa2" and req.body.favorites.name = "Hang Clean"
User.findByIdAndUpdate(_user._id, {
'profiles._id': req.body._id
}, {
$pull: {
'profiles.$.favorites': {
'name': req.body.favorites.name
}
}
}, {
safe: true,
upsert: true
},
function(err, model) {
if (err) {
console.log(err);
return res.status(500).send('Error Deleting Profile');
}
return res.status(200).send('Profile Deleted!');
});
Try updating using the findOneAndUpdate() method since you are supplying the findByIdAndUpdate() method with the wrong parameters: the second argument { 'profiles._id': req.body._id } should be part of the first query object hence you need to use the findOneAndUpdate() method as follows, making sure you convert the string ids into ObjectId's:
var mongoose = require('mongoose');
var id = mongoose.Types.ObjectId(_user._id),
profileId = mongoose.Types.ObjectId(req.body._id),
query = {
"_id": id,
"profiles._id": profileId
},
update = {
"$pull": {
"profiles.$.favorites": { "name": req.body.favorites.name }
}
},
options = { "multi": true, "upsert": true };
User.findOneAndUpdate(query, update, options, function(err, model) {
if(err){
console.log(err);
return res.status(500).send('Error Deleting Profile');
}
return res.status(200).send('Profile Deleted!');
});

Resources