Deleting array object in Meteor/MongoDB - mongodb

I'm trying to delete an Object inside an array, I have tried both pull and unset in Meteor.update but the entire array is being deleted instead of just the individual object inside the array.
Code for what I've tried
Cars.update(
{ _id: id},
{ $unset: { 'models': { '$.id': modelId } } })
And
Cars.update(
{ _id: id},
{ $pull: { 'models': { 'id': modelId } } })
In both cases, the entire 'models' attribute was deleted instead of just an object from the array
Schema for Cars:
models : {
type: [Object],
optional: true
},
'models.$.id': {
type: String,
autoValue: function() {
return Meteor.uuid()
},
optional: true
}
Essentially, the collections 'Cars' contains an array 'models' which is an array of objects (car models). Each car model object has an attribute id. I want to delete an individual car model from the models array using the attribute 'id' but my above attempts deleted the entire 'models' array instead of the individual object. Any help would be appreciated. Thanks
Before
{
"_id" : "XxbKzS6GHthxwnLFq",
"createdAt" : ISODate("2018-05-04T08:05:59.151Z"),
"updatedAt" : ISODate("2018-05-04T08:36:11.785Z"),
"models" : [
{
"name" : "Mercedes",
"id" : "9927cfe1-f5ae-4625-b6eb-87868793a229"
},
{
"name" : "BMW",
"id" : "86f24e9d-dd08-4407-b350-63d9b25dc094"
}
]
}
After
{
"_id" : "XxbKzS6GHthxwnLFq",
"createdAt" : ISODate("2018-05-04T08:05:59.151Z"),
"updatedAt" : ISODate("2018-05-04T09:38:56.470Z")
}
The parent object is XxbKz... and it contains an attribute models which is an array of objects (BMW,Mercedes). I want to delete the BMW object from the XxbKz parent object. I queried the parent object using its id (XxbKz...) and the BMW object using its id (86f2...) as well (code in original post). The result was that the entire models array got deleted (both BMW and Mercedes) instead of just BMW.
My Meteor call was
Meteor.call('deleteCar','XxbKzS6GHthxwnLFq','86f24e9d-dd08-4407-b350-
63d9b25dc094')
deleteCar(carId, modelId) {
check(carId, String)
check(modeld, String)
if (Meteor.isServer) {
let car = Cars.update( {_id: carId},
{ $pull: { 'models': { 'id': modelId } } }
)
console.log(car)
return car
}
}
}
The variables in the deleteCar function are carId(XxbK) and modelId(86f2) which are the first and second parameters from the deleteCar meteor call. From my understanding, just the BMW from the parent should have been deleted but for some reason this is not the case

This works to remove Mercedes from your example.
db.cars.update({_id: 'XxbKzS6GHthxwnLFq'}, {$pull: {models: {id: '9927cfe1-f5ae-4625-b6eb-87868793a229'}}})
Looks just like
Cars.update(
{ _id: id},
{ $pull: { 'models': { 'id': modelId } } })
I did run this from the mongo shell and not meteor. However I have used $pull from within Meteor no problem.
I have never used the return update, it may well be returning the original document before updating. I would add this instead.
Cars.update( {_id: carId},
{ $pull: { 'models': { 'id': modelId } } }
)
const car = Cars.findOne(carId)
console.log(car)
return car
Good luck and let me know how you get on.

Related

$push is not supported in mongoose bulk operation

I am using the bulk operation in mongoose to update multiple documents in a concurrent manner.
The update data looks as below:
let docUpdates = [
{
id : docId1,
status : "created",
$push : {
updates : {
status: "created",
referenceId : referenceId,
createdTimestamp : "2022-06-14T00:00:00.000Z"
}
}
},
{
id : docId2,
status : "completed",
$push : {
updates : {
status: "compelted",
referenceId : referenceId,
createdTimestamp : "2022-06-14T00:00:00.000Z"
}
}
},
];
// Update documents using bulk operation
let bulkUpdate = Model.collection.initializeOrderedBulkOp();
for (let i = 0; i < docUpdates.length; i++) {
bulkUpdate.find({_id : docUpdates[i].docId}).updateOne({ $set : docUpdates[i]})
}
When I execute the above snippet, I get the below error.
(node:468) UnhandledPromiseRejectionWarning: MongoBulkWriteError: The dollar ($) prefixed field '$push' in '$push' is not allowed in the context of an update's replacement document. Consider using an aggregation pipeline with $replaceWith.
at OrderedBulkOperation.handleWriteError (/usr/src/app/node_modules/mongoose/node_modules/mongodb/lib/bulk/common.js:884:22)
Could you please help in finding a work around for this error.
Thank you,
KK
The problem isn't that $push isn't supported, it's that the way you pass the arguments in the update document, it's interpreted as a top-level field. See the documentation about field name considerations, specifically the section on document modifying updates.
This section of your code updateOne({ $set : docUpdates[i]}) is equivalent to the following:
updateOne({
$set: {
id: docId1,
status: "created",
$push: {
updates: {
status: "created",
referenceId: referenceId,
createdTimestamp: "2022-06-14T00:00:00.000Z"
}
}
}
})
Because the $push is contained in the $set, you're telling MongoDB to set the value of a field named $push to a document with a field called updates, which has status, referenceId, and createdTimestamp fields.
What you want to do instead, is specify an update document with two different update operators. In other words, $push needs to be on the same level as the $set.
Something like:
updateOne({
$set: {
id: docId1,
status: "created"
},
$push: {
updates: {
status: "created",
referenceId: referenceId,
createdTimestamp: "2022-06-14T00:00:00.000Z"
}
}
})
How you represent that in the docUpdates array is up to you. Maybe you can do something like:
let docUpdates = [
{
id : docId1,
update : {
$set : {
status : "created"
},
$push : {
updates : {
status: "created",
referenceId : referenceId,
createdTimestamp : "2022-06-14T00:00:00.000Z"
}
}
}
}
// ... Other updates
]
And use it in the loop like:
bulkUpdate.find({_id : docUpdates[i].docId}).updateOne(docUpdates[i].update)

Insert date stamp into new entry into an array

In a MongoDB collection, I want to add a new object to an array which may or may not already exist, with the creation date of the object as one of the fields. Here's a barebones version of the structure of my test collection, showing the result that I want to achieve:
{
"_id" : 1,
"parent" : {
"array_1" : [
{ "key" : "value_1"
, "created": ISODate(...)
}
]
}
}
In meteor mongo, I create the parent with the following commands:
use test
db.test.insert({ _id: 1, parent: {} })
I can add a new array with the following command:
db.test.update(
{ _id: 1 }
, { $push: {
"parent.array_1": {
key: "value_1"
}
}
}
, { $currentDate:
{ "parent.array_1.$.created": true }
}
)
However, in the resulting array, the created field is not added. I need to use a second command which refers to the array in the selection query:
db.test.update(
{ _id: 1
, "parent.array_1.key": "value_1"
}
, { $currentDate:
{ "parent.array_1.$.created": true}
}
)
Is there a way to insert the created field at the same time as the array item that it belongs to, in a single command?
You are passing 3 arguments to update. The second argument is supposed to be the update, and the third is supposed to be an options object. If you want to include the create time in the array element, make sure it is in the object that you are pushing on the array. Since you are using javascript, that could be:
db.test.update(
{ _id: 1 }
, { $push: {
"parent.array_1": {
key: "value_1",
created: new Date()
}
}
}
)

What is the better implementation? MongoDb Query

I need some help with MongoDb. I need to check if an object exists in the database. If it's true, then I need to check if this object has a specific element into array (Products). If not, I need to create this object(Order) with this element(Cookie) in to array(Products).
Example data:
Order {
_id: ObjectId("580bc55f54101f1d18152d88"),
code: "AVG223424",
products: [
{
name: "Cookie"
},
{
name: "Soda"
}
]
}
Finally, what is the better implementation?
Assuming you are using a collection with the name 'Orders'
db
.Orders
.update({
code: "12345"
}, {
$addToSet: {
products: {
name: "Cookie"
}
},
$setOnInsert: {
code: "12345"
}
}, {
upsert: true
});
This query looks for a document with the same 'code,' and if found, will add the object '{name: "Cookie"}' if there is no other Object with the same key/val pairs. If the document does not exist, the '$setOnInsert' command will set the specified fields only if a new document is created.

MongoDB - How to find() a field in a collection that has a reference to another collection?

So I have this field contacts.envCon.name which is inside the Projects collection but when I see them in mongo they are like this:
"envCon" : {
"$ref" : "contacts",
"$id" : ObjectId("5807966090c01f4174cb1714")
}
After doing a simple find based on past ObjectId:
db.getCollection('contacts').find({_id:ObjectId("5807966090c01f4174cb1714")})
I get the following result:
{
"_id" : ObjectId("5807966090c01f4174cb1714"),
"name" : "Terracon"
}
By the way: I'm using Meteor if there is anyway to do this directly with publish/suscribe methods.
Yes, you can do this join inside a publication using the very popular reywood:publish-composite package.
With your model:
Meteor.publishComposite('projectsWithContacts', {
find: function() {
return Projects.find(); // all projects
},
children: [
{
find: function(p) { // p is one project document
return Contacts.find(
{ _id: p.envCon.$id }, // this is the relationship
{ fields: { name: 1 } }); // only return the name (_id is automatic)
}
},
]
});

MongoDB conditionally $addToSet sub-document in array by specific field

Is there a way to conditionally $addToSet based on a specific key field in a subdocument on an array?
Here's an example of what I mean - given the collection produced by the following sample bootstrap;
cls
db.so.remove();
db.so.insert({
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples"
}
]
});
n defines a unique document key. I only want one entry with the same n value in the array at any one time. So I want to be able to update the pfms array using n so that I end up with just this;
{
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples",
"mState": 1111234
}
]
}
Here's where I am at the moment;
db.so.update({
"Name": "fruitBowl",
},{
// not allowed to do this of course
// "$pull": {
// "pfms": { n: "apples" },
// },
"$addToSet": {
"pfms": {
"$each": [
{
"n": "apples",
"mState": 1111234
}
]
}
}
}
)
Unfortunately, this adds another array element;
db.so.find().toArray();
[
{
"Name" : "fruitBowl",
"_id" : ObjectId("53ecfef5baca2b1079b0f97c"),
"pfms" : [
{
"n" : "apples"
},
{
"n" : "apples",
"mState" : 1111234
}
]
}
]
I need to effectively upsert the apples document matching on n as the unique identifier and just set mState whether or not an entry already exists. It's a shame I can't do a $pull and $addToSet in the same document (I tried).
What I really need here is dictionary semantics, but that's not an option right now, nor is breaking out the document - can anyone come up with another way?
FWIW - the existing format is a result of language/driver serialization, I didn't choose it exactly.
further
I've gotten a little further in the case where I know the array element already exists I can do this;
db.so.update({
"Name": "fruitBowl",
"pfms.n": "apples",
},{
$set: {
"pfms.$.mState": 1111234,
},
}
)
But of course that only works;
for a single array element
as long as I know it exists
The first limitation isn't a disaster, but if I can't effectively upsert or combine $addToSet with the previous $set (which of course I can't) then it the only workarounds I can think of for now mean two DB round-trips.
The $addToSet operator of course requires that the "whole" document being "added to the set" is in fact unique, so you cannot change "part" of the document or otherwise consider it to be a "partial match".
You stumbled on to your best approach using $pull to remove any element with the "key" field that would result in "duplicates", but of course you cannot modify the same path in different update operators like that.
So the closest thing you will get is issuing separate operations but also doing that with the "Bulk Operations API" which is introduced with MongoDB 2.6. This allows both to be sent to the server at the same time for the closest thing to a "contiguous" operations list you will get:
var bulk = db.so.initializeOrderedBulkOp();
bulk.find({ "Name": "fruitBowl", "pfms.n": "apples": }).updateOne({
"$pull": { "pfms": { "n": "apples" } }
});
bulk.find({ "Name": "fruitBowl" }).updateOne({
"$push": { "pfms": { "n": "apples", "state": 1111234 } }
})
bulk.execute();
That pretty much is your best approach if it is not possible or practical to move the elements to another collection and rely on "upserts" and $set in order to have the same functionality but on a collection rather than array.
I have faced the exact same scenario. I was inserting and removing likes from a post.
What I did is, using mongoose findOneAndUpdate function (which is similar to update or findAndModify function in mongodb).
The key concept is
Insert when the field is not present
Delete when the field is present
The insert is
findOneAndUpdate({ _id: theId, 'likes.userId': { $ne: theUserId }},
{ $push: { likes: { userId: theUserId, createdAt: new Date() }}},
{ 'new': true }, function(err, post) { // do the needful });
The delete is
findOneAndUpdate({ _id: theId, 'likes.userId': theUserId},
{ $pull: { likes: { userId: theUserId }}},
{ 'new': true }, function(err, post) { // do the needful });
This makes the whole operation atomic and there are no duplicates with respect to the userId field.
I hope this helpes. If you have any query, feel free to ask.
As far as I know MongoDB now (from v 4.2) allows to use aggregation pipelines for updates.
More or less elegant way to make it work (according to the question) looks like the following:
db.runCommand({
update: "your-collection-name",
updates: [
{
q: {},
u: {
$set: {
"pfms.$[elem]": {
"n":"apples",
"mState": NumberInt(1111234)
}
}
},
arrayFilters: [
{
"elem.n": {
$eq: "apples"
}
}
],
multi: true
}
]
})
In my scenario, The data need to be init when not existed, and update the field If existed, and the data will not be deleted. If the datas have these states, you might want to try the following method.
// Mongoose, but mostly same as mongodb
// Update the tag to user, If there existed one.
const user = await UserModel.findOneAndUpdate(
{
user: userId,
'tags.name': tag_name,
},
{
$set: {
'tags.$.description': tag_description,
},
}
)
.lean()
.exec();
// Add a default tag to user
if (user == null) {
await UserModel.findOneAndUpdate(
{
user: userId,
},
{
$push: {
tags: new Tag({
name: tag_name,
description: tag_description,
}),
},
}
);
}
This is the most clean and fast method in the scenario.
As a business analyst , I had the same problem and hopefully I have a solution to this after hours of investigation.
// The customer document:
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK"
}
]
}
// The problem : I want to insert dateField "01.01.2016" to customer documents where customerCodes subdocument has a document with code "YK" but does not have dateField. The final document must be as follows :
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK" ,
"dateField" : "01.01.2016"
}
]
}
// The solution : the solution code is in three steps :
// PART 1 - Find the customers with customerCodes "YK" but without dateField
// PART 2 - Find the index of the subdocument with "YK" in customerCodes list.
// PART 3 - Insert the value into the document
// Here is the code
// PART 1
var myCursor = db.customers.find({ customerCodes:{$elemMatch:{code:"YK", dateField:{ $exists:false} }}});
// PART 2
myCursor.forEach(function(customer){
if(customer.customerCodes != null )
{
var size = customer.customerCodes.length;
if( size > 0 )
{
var iFoundTheIndexOfSubDocument= -1;
var index = 0;
customer.customerCodes.forEach( function(clazz)
{
if( clazz.code == "YK" && clazz.changeDate == null )
{
iFoundTheIndexOfSubDocument = index;
}
index++;
})
// PART 3
// What happens here is : If i found the indice of the
// "YK" subdocument, I create "updates" document which
// corresponds to the new data to be inserted`
//
if( iFoundTheIndexOfSubDocument != -1 )
{
var toSet = "customerCodes."+ iFoundTheIndexOfSubDocument +".dateField";
var updates = {};
updates[toSet] = "01.01.2016";
db.customers.update({ "id" : customer.id } , { $set: updates });
// This statement is actually interpreted like this :
// db.customers.update({ "id" : "1212" } ,{ $set: customerCodes.0.dateField : "01.01.2016" });
}
}
}
});
Have a nice day !