Below is the data fields i have in my collection.
{ "_id" : "TLa7L9HJTabD6ooLJ", "userId" : "dgS4gJtoEPoRrGE2b", "data" : { "contact" : [ "firstname", "lastname", "phone", "email", "leadsource" ] } }
i am try to replace data.contact array value with new array i am using this query
setting.update({
_id: doc.userId
}, {
$set: {
"data.contact":["data1","data2"]
}
},function(error){
if(error){
console.log(error.reason);
}else{
toastr.success('User Details updated.');
}
});
Once my above query run i got success message but in Database this value
"data.contact":["data1","data2"] still not updated.
Are you sure you are not mistaken in this line:
{
_id: doc.userId
}
shouldn't it be:
{
userId: doc.userId
}
because you have such field in database. Maybe it's just a simple mistake.
Related
I would like to retrieve records from contacts array whose ContactGroup does not have Club, i.e for the below data I shall get only object with ContactName as Ravi.
Data in MongoDB
The below is my query (on Mongo DB M0)
db.phonenumbers.aggregate(
[{$match:{"userName" : "Veer"}},
{$unwind : "$contacts"},
{$unwind : "$contacts.ContactGroup"},
{$match:{"contacts.ContactGroup":{$ne:"Club"}}},
{$project : { "contacts.ContactName":1,"contacts.ContactGroup":1,_id : 0}}
])
Notice the Result, I am supposed to get only Ravi but I get others too.
Please advice how I can fix the query ?
{ contacts: { ContactName: 'Ravi', ContactGroup: 'Family' } }
{ contacts: { ContactName: 'Ravi', ContactGroup: 'Business' } }
{ contacts: { ContactName: 'Ram', ContactGroup: 'Friends' } }
{ contacts: { ContactName: 'Santosh', ContactGroup: 'Business' } }
You are unwinding contacts.ContactGroup and then matching by it. Try to reverse the order of these actions. That will filter out all objects that does not have "Club" value in the ContactGroup array.
db.phonenumbers.aggregate([
{ $match:{"userName" : "Veer"} },
{ $unwind : "$contacts" },
{ $match:{"contacts.ContactGroup":{$ne:"Club"}} },
{ $unwind : "$contacts.ContactGroup" },
{ $project : { "contacts.ContactName":1,"contacts.ContactGroup":1,_id : 0} }
])
I'm trying to update some documents from DB-collection1 (source db) over to DB-collection2 (destination DB) .. all on the same MongoDb (with same permissions, etc).
So for each document from DB-Collection1, update a specific document in DB-collectoin2, if it exists.
The documents in DB-collection1 have following shape:
{
"_id": {
"commentId": "082f3de6-a268-46b5-803f-89bafd172621"
},
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"type": "Document"
}
]
}
}
And the matching document in DB-collection2 is:
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"name": "jill"
},
I'm using a cursor to iterate through the source collection but I'm not sure how I can do this?
This is the javascript code mongo shell script I'm trying right now, when I run the following command on a machine where mongo is installed:
CLI: root#f0cc2f13e70c:/src/scripts# mongo --host localhost --username root --password example copyFoosToBars.js
// copyFoosToBars.js
function main() {
print('Starting script.')
print()
var foosDb = db.getSiblingDB('foos');
var barsDb = db.getSiblingDB('bars');
// Grab all the 'foos' which have a some barId in some convoluted schema.
var sourceFoos = foosDb.getCollection('foos')
.find(
{
"appliesTo.targets.type" : "Document",
"_meta.deleted": null
},
{
"_id" : 0,
"appliesTo.targets._id.documentId" : 1
}
);
sourceFoos.forEach(function(foo){
// Check if this document exists in the bars-db
var desinationBars = barsDb.getCollection('bars')
.find(
{
"_id.documentId" : foo.appliesTo.targets._id.documentId,
},
);
printjson(desinationBars);
// If destinationBars document exists, then add a the field 'Text' : 'hi there' to the document -or- update the existing field, if the 'Text' field already exists in this document.
});
print()
print()
print('----------------------------------------------')
}
main()
So here's some sample json output for the first part of the query -> which proves that I have some data which passes that 'find/search' clause:
Starting script.
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "810e66e2-66d1-44f4-be0e-980309d8df8f"
}
}
]
}
}
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "54f25223-67bb-4d5d-ad47-24392e4acbdf"
}
}
]
}
}
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "34c83da3-eafd-41bf-93af-3a45d1644225"
}
}
]
}
}
This doesn't work.
MongoDB server version: 4.0.22
WARNING: shell and server versions do not match
Starting script.
uncaught exception: TypeError: comment.appliesTo.targets._id is undefined :
main/
<snip>
Can someone please suggest some clues as to how I can fix this, please?
First of all you need to safeguard against multiple items in the appliesTo.targets.
A document
{
"_id": {
"commentId": "082f3de6-a268-46b5-803f-89bafd172621"
},
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "should-not-be-updated"
},
"type": "AnyOtherType"
},
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"type": "Document"
}
]
}
}
Will be selected by
.find(
{
"appliesTo.targets.type" : "Document",
"_meta.deleted": null
},
{
"_id" : 0,
"appliesTo.targets._id.documentId" : 1
}
);
with the resulting document:
{
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "should-not-be-updated"
}
},
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
}
}
]
}
}
so foo.appliesTo.targets[0]._id.documentId will be "should-not-be-updated".
Structure of the document does not allow to use $elemMatch, so you have to either use aggregation framework or filter the array clientside. The aggregation has benefit of running serverside and reduce amount of data to transfer to the client.
Secondly, there is no point to find documents from DB-collection2. You can update all matching ones straight away, like in "update...where" SQL .
So the code must be something like following:
var sourceFoos = db.foos.aggregate([
{
$unwind: "$appliesTo.targets"
},
{
$match: {
"appliesTo.targets.type": "Document",
"appliesTo.targets._id.documentId": {
$exists: true
},
"_meta.deleted": null
}
},
{
$project: {
_id: 0,
"documentId": "$appliesTo.targets._id.documentId"
}
}
]);
sourceFoos.forEach(function(foo){
db.bars.updateMany(
{"_id.documentId" : foo.documentId},
{$set:{'Text' : 'hi there'}}
)
})
If there are a lot of documents expected in the cursor I would recommend to look at bulk updates to speed it up, but as I mentioned earlier in this case mongo shell might not be an ideal tool.
target is an array so for accessing to the _id try like this :
foo.appliesTo.targets[0]._id.barId
use async/await with try/catch and use .toArray() after find query
// copyFoosToBars.js
async function main() {
try {
var foosDb = db.getSiblingDB("foos");
var barsDb = db.getSiblingDB("bars");
// Grab all the 'foos' which have a some barId in some convoluted schema.
var sourceFoos = await foosDb
.getCollection("foos")
.find(
{
"appliesTo.targets.type": "Bar",
"_meta.deleted": null,
},
{
_id: 0,
"appliesTo.targets._id.fooId": 1,
}
).toArray();
for (foo of sourceFoos) {
var desinationBars = await barsDb
.getCollection("bars")
.find({
"_id.barId": foo.appliesTo.targets[0]._id.barId,
})
.toArray();
console.log(desinationBars);
if(desinationBars.length> 0){
//do somthings
}
}
} catch (error) {
console.log(error)
}
}
in the first find query you select "appliesTo.targets._id.fooId" : 1so select ( fooId but in the result json show "barId" : "810e66e2-66d1-44f4-be0e-980309d8df8f" it's conflict, Anyway, I hope this solution solves your problem
I'm having trouble with something that thought would be trivial in MongoDB with Mongoose.
With a fairly simple schema like this
const UserSchema = new Schema({
groups: [
{
name: String,
members: [
{
hasAccepted: {
type: Boolean
}
}
]
}
]
});
When i create new groups, each member object gets an _id property of course. I simply want to select that member by its _id and update its hasAccepted property.
When I run a query with the _id of the member, I get back the entire record for the user, which makes it difficult to find the nested member to update it.
How can I trim the result down to just the member with the found ID and update its property?
I'm using Mongo 3.6.2 and have tried the new arrayFilters, but with no luck.
My code (using Node) is below, which returns the whole document, but with nothing updated.
const query = {
groups : {
$elemMatch : { members : { $elemMatch : {_id : <id>} } }
}
};
const update = {$set: {'groups.$[].members.$[o].hasAccepted':true }};
const options = { new: true, arrayFilters:[{"o._id":<id>}] };
// Find the document
User.findOneAndUpdate(query, update, options, function(error, result) {
if (error) {
res.send(error);
} else {
res.send(result);
}
});
EDIT: here's the full data from the test db i'm working with. The _id I've been testing with is one the for the member in Group 1: 5a753f168b5b7f0231ab0621
[
{
"_id": {
"$oid": "5a7505452f93de2c90f49a20"
},
"groups": [
{
"name": "Group 2",
"_id": {
"$oid": "5a7543b8e254ab02cd728c42"
},
"members": [
{
"user": {
"$oid": "5a7543b8e254ab02cd728c41"
},
"_id": {
"$oid": "5a7543b8e254ab02cd728c43"
},
"hasAccepted": false
}
]
},
{
"name": "Group 1",
"_id": {
"$oid": "5a753f168b5b7f0231ab0620"
},
"members": [
{
"user": {
"$oid": "5a753f168b5b7f0231ab061f"
},
"_id": {
"$oid": "5a753f168b5b7f0231ab0621"
},
"hasAccepted": false
}
]
}
]
},
{
"_id": {
"$oid": "5a753f168b5b7f0231ab061f"
},
"groups": [],
},
{
"_id": {
"$oid": "5a7543b8e254ab02cd728c41"
},
"groups": [],
}
]
Thanks for any help you can offer.
OK, so it turns out the the thing I needed to understand better are arrayFilters (that, and I needed to add the group name into the data I used to get to the value I needed to updated.
The thing that helped me understand arrayFilters the best was to think of the as a sort of subquery, like is used in the SQL world. Once I got that, I was able to figure out how to write my update.
This article was also very helpful in understanding how arrayFilters are used: http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html
Here's the code that worked for me. Note that you need Mongo 3.6 and Mongoose 5.0.0 to get support for arrayFilters.
Also, you need to be sure to require Mongoose's ObjectId like so
const ObjectId = require('mongoose').Types.ObjectId;
Here's the rest of the working code:
const query = {
groups : {
$elemMatch : { members : { $elemMatch : {_id : new ObjectId("theideofmymemberobject"), hasAccepted : false} } }
}
};
const update = {$set: {'groups.$[group].members.$[member].hasAccepted':true } };
const options = { arrayFilters: [{ 'group.name': 'Group 3' },{'member._id': new ObjectId("theideofmymemberobject")}] };
// update the document
User.update(query, update, options, function(error, result) {
if (error) {
res.send(error);
} else {
res.send(result);
}
});
You can try below aggregation to filter only the matching group and member from the group and member arrays
replace _id with the id to be searched, use the result to update the hasAccepted status
db.groups.aggregate(
[
{$addFields : {"groups" : {$arrayElemAt : [{$filter : {input : "$groups", as : "g", cond : {$in : [_id, "$$g.members._id"]}}}, 0]}}},
{$addFields : {"groups.members" : {$filter : {input : "$groups.members", as : "gm", cond : {$eq : [_id, "$$gm._id"]}}}}}
]
).pretty()
// An easier more modern way to do this is to pass a wildcard such as /:id in your API endpoint
// Use Object.assign()
// use the save() method
// If you are using JWT
// Otherwise find another unique identifier
const user = UserSchema.findOne({ id: req.user.id });
for (const oldObject of user.groups) {
if(oldObject.id === req.params.id) {
newObject = {
propertyName: req.body.val,
propertyName2: req.body.val2,
propertyName3: req.body.val3
}
// Update the oldObject
Object.assign(oldObject, newObject);
break;
}
}
user.save()
res.json(user);
document structure
session document:
{
"_id" : ObjectId("5648b811a16bc2e0355b8289"),
"title" : "test",
"messages" : [
{
"authorId" : ObjectId("5648b89f2fc311bc39073993"), // <-- the users id
"created" : ISODate("2015-11-15T16:51:29.652Z"),
"message" : "<p>test</p>"
}
]
}
user document:
"_id" : ObjectId("5648b89f2fc311bc39073993"),
"pic" : "karl.png",
"profile" : {
"name" : "Karl Morrison"
}
queries
first query:
var id = '5648b811a16bc2e0355b8289'; // <-- id for the session document
var session = // <-- returns the session document
yield sessions.findOne({
_id: id,
});
second query (this is where I am stuck):
var sessionUsers =
yield new Promise(function (resolve, reject) {
users.col.aggregate([
{
$match: {
'_id': {
'$in': session.messages.authorId // <--- here I need help! Need all of the authorId's in the session document from the previous query result.
}
}
},
{
$project: {
_id: 1,
pic: 1,
name: '$profile.name'
}
}
],
function (err, res) {
if (err === null)
resolve(res);
reject(err);
});
});
question
So I am trying to get all of the authorId's into an array and feed that array into the $in of the second query, which should return an array full of elements with the fields of _id, pic and name.
So sessionUsers variable should look something like this:
[{
"_id" : ObjectId("5648b89f2fc311bc39073993"),
"pic" : "karl.png",
"name" : "Karl Morrison"
},...]
I am not sure if I understand the problem correctly, but I think you are trying to send an array of objects instead of an array of _id's behind your $match. You will need to get all _id's in a simple array and use the result. You can use lodash. Here are the docs for more information.
The code below "plucks" all _id's from your session data.
var ids = _.pluck(session.messages, 'author‌Id');
Next, change the $match part to this:
{
$match: {
'_id': {
'$in': ids
}
}
}
I have the following documents:
[{
"_id":1,
"name":"john",
"position":1
},
{"_id":2,
"name":"bob",
"position":2
},
{"_id":3,
"name":"tom",
"position":3
}]
In the UI a user can change position of items(eg moving Bob to first position, john gets position 2, tom - position 3).
Is there any way to update all positions in all documents at once?
You can not update two documents at once with a MongoDB query. You will always have to do that in two queries. You can of course set a value of a field to the same value, or increment with the same number, but you can not do two distinct updates in MongoDB with the same query.
You can use db.collection.bulkWrite() to perform multiple operations in bulk. It has been available since 3.2.
It is possible to perform operations out of order to increase performance.
From mongodb 4.2 you can do using pipeline in update using $set operator
there are many ways possible now due to many operators in aggregation pipeline though I am providing one of them
exports.updateDisplayOrder = async keyValPairArr => {
try {
let data = await ContestModel.collection.update(
{ _id: { $in: keyValPairArr.map(o => o.id) } },
[{
$set: {
displayOrder: {
$let: {
vars: { obj: { $arrayElemAt: [{ $filter: { input: keyValPairArr, as: "kvpa", cond: { $eq: ["$$kvpa.id", "$_id"] } } }, 0] } },
in:"$$obj.displayOrder"
}
}
}
}],
{ runValidators: true, multi: true }
)
return data;
} catch (error) {
throw error;
}
}
example key val pair is: [{"id":"5e7643d436963c21f14582ee","displayOrder":9}, {"id":"5e7643e736963c21f14582ef","displayOrder":4}]
Since MongoDB 4.2 update can accept aggregation pipeline as second argument, allowing modification of multiple documents based on their data.
See https://docs.mongodb.com/manual/reference/method/db.collection.update/#modify-a-field-using-the-values-of-the-other-fields-in-the-document
Excerpt from documentation:
Modify a Field Using the Values of the Other Fields in the Document
Create a members collection with the following documents:
db.members.insertMany([
{ "_id" : 1, "member" : "abc123", "status" : "A", "points" : 2, "misc1" : "note to self: confirm status", "misc2" : "Need to activate", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") },
{ "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") }
])
Assume that instead of separate misc1 and misc2 fields, you want to gather these into a new comments field. The following update operation uses an aggregation pipeline to:
add the new comments field and set the lastUpdate field.
remove the misc1 and misc2 fields for all documents in the collection.
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ], lastUpdate: "$$NOW" } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
Suppose after updating your position your array will looks like
const objectToUpdate = [{
"_id":1,
"name":"john",
"position":2
},
{
"_id":2,
"name":"bob",
"position":1
},
{
"_id":3,
"name":"tom",
"position":3
}].map( eachObj => {
return {
updateOne: {
filter: { _id: eachObj._id },
update: { name: eachObj.name, position: eachObj.position }
}
}
})
YourModelName.bulkWrite(objectToUpdate,
{ ordered: false }
).then((result) => {
console.log(result);
}).catch(err=>{
console.log(err.result.result.writeErrors[0].err.op.q);
})
It will update all position with different value.
Note : I have used here ordered : false for better performance.