Filter arrays in mongodb - mongodb

I have a collection where each document contains 2 arrays of documents as below.
{
all_users:[
{
id:1,
name:"A"
},
{
id:2,
name:"B"
},
{
id:3,
name:"C"
}
]
selected_users:[
{
id:1,
name:"A"
},
{
id:2,
name:"B"
}
]
}
Is it possible to merge the 2 arrays by adding a field selected and assigning a value to it based on whether the name of the user is present in a document in the selected_users array as follows.
{
all_users:[
{
id:1,
name:"A",
selected:"yes"
},
{
id:2,
name:"B",
selected:"yes"
},
{
id:3,
name:"C",
selected:"no"
}
]
}
I want to check by id or name since the documents may contain additional fields. I can't figure out how to achieve this.

$map to iterate loop of all_users array
$cond check condition if id is in selected users id then return "yes" otherwise "no" in selected field
$mergeObject to merge current user object with above selected field
db.collection.aggregate([
{
$project: {
all_users: {
$map: {
input: "$all_users",
in: {
$mergeObjects: [
"$$this",
{
selected: {
$cond: [
{ $in: ["$$this.id", "$selected_users.id"] },
"yes",
"no"
]
}
}
]
}
}
}
}
}
])
Playground

If you are using the Robo Mongo 3T, you can use JavaScript, so you can do something like that:
db.collection.find({}).forEach(doc => {
doc.all_users.map(au => {
const su = doc.selected_users.find(su => su.id === au.id);
au.selected = su === undefined;
});
delete doc.selected_users;
db.collection.save(doc);
})
I would advise you to create a Backup of the collection beforehand. :)

Related

MongoDB - Update data type for the nested documents

I have this collection: (see the full collection here https://mongoplayground.net/p/_gH4Xq1Sk4g)
{
"_id": "P-00",
"nombre": "Woody",
"costo": [
{
"tipo": "Cap",
"detalle": "RAC",
"monto_un": "7900 ",
"unidades": "1",
"total": "7900 "
}
]
}
I tried a lot of ways to transform monto_un, unidades and total into int, but I always get an error.
Neither of these works.
db.proyectos.updateMany({}, {'$set': {"costo.monto_un": {'$toInt': 'costo.$.monto_un'}}})
db.collection.update({},
[
{
$set: {
costo: {
monto_un: {
$toInt: {
costo: "$monto_un"
}
}
}
}
}
],
{
multi: true
})
MongoDB 5.0.9
Any suggestions?
$set - Update costo array.
1.1. $map - Iterate each element in the costo array and return a new array.
1.2. $mergeObjects - Merge current document with the document from 1.3.
1.3. A document with the monto_un field. You need to trim space for the monto_un field in the current iterate document via $trim and next convert it to an integer via $toInt.
In case you are also required to convert the unidades and total as int, add those fields with the same operator/function logic as monto_un in 1.3. Those fields in the document (1.3) will override the existing value due to $mergeObjects behavior.
db.collection.update({},
[
{
$set: {
costo: {
$map: {
input: "$costo",
in: {
$mergeObjects: [
"$$this",
{
monto_un: {
$toInt: {
$trim: {
input: "$$this.monto_un"
}
}
}
}
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground

MongoDB - How to rename the specific field from list of unstructured array field?

I have several documents as given below. Now I need to do rename the middlename field into mid_name if middlename exists in the document.
{
"id":"abc",
"name":[
{
"first_name":"abc",
"last_name":"def"
},
{
"first_name":"ghi",
"last_name":"mno",
"middilename":"xyz"
}
]
}
This is something that I expect it to be.
{
"id":"abc",
"name":[
{
"first_name":"abc",
"last_name":"def"
},
{
"first_name":"ghi",
"last_name":"mno",
"mid_name":"xyz"
}
]
}
And this is what I have done but it throws the error.
db.md_carrierInformation.updateMany({"name.middlename":{$exists:true}}, {$rename:{"name.$.middlename":"name.mid_name"}})
ERROR
MongoServerError: The source field for $rename may not be dynamic: name.$.middlename
Work on the update with the aggregation pipeline.
$set - Set name array field.
1.1. $map - Iterate each item in name array and return new array.
1.2. $cond - Condition for checking current document's middlename is not existed.
1.2.1. If true, with merge current document with the document with field mid_name via $mergeObjects.
1.2.2. If false, remain the existing document.
$unset - Remove field for name.middlename.
db.md_carrierInformation.updateMany({
"name.middlename": {
$exists: true
}
},
[
{
$set: {
"name": {
$map: {
input: "$name",
in: {
$cond: {
if: {
$ne: [
"$$this.middlename",
undefined
]
},
then: {
$mergeObjects: [
"$$this",
{
mid_name: "$$this.middlename"
}
]
},
else: "$$this"
}
}
}
}
}
},
{
$unset: "name.middlename"
}
])
Sample Mongo Playground

MongoDB4 update nested objects item

i've a problem updating a nested object in MongoDB 4.
I wrote a playground where I'm doing some tests to be able to solve the problem ..
My desiderata is:
I need the "email" item to be overwritten with the lowercase of itself (result obtained).
I need that in all the N objects contained in the "emailsStatus" array the "emailAddress" item is overwritten with itself but in lowercase
I can not carry out the second point, in the playground you will find everything ready and with the test I am carrying out but I am not succeeding .. What am I wrong?
Playground: https://mongoplayground.net/p/tJ25souNlYZ
Try $map to iterate loop of emailsStatus array and convert emailAddress lower case, merge current object with update email field using $mergeObjects,
one more suggestion for query part is you can check single condition for email using $type is string
db.collection.update(
{ email: { $type: "string } },
[{
$set: {
email: { $toLower: "$email" },
emailsStatus: {
$map: {
input: "$emailsStatus",
in: {
$mergeObjects: [
"$$this",
{ emailAddress: { $toLower: "$$this.emailAddress" } }
]
}
}
}
}
}],
{ multi: true }
)
Playground
Check null condition in emailAddress array using $cond and $type,
$map: {
input: "$emailsStatus",
in: {
$mergeObjects: [
"$$this",
{
emailAddress: {
$cond: [
{ $eq: [{ $type: "$$this.emailAddress" }, "string"] },
{ $toLower: "$$this.emailAddress" },
"$$this.emailAddress"
]
}
}
]
}
}
Playground

delete element out of array with $pull and $cond operators

I want to pull elements out of the array only if some condition is met
This is my document structure:
{
_id: "userId",
posts: [{
_id: "postId",
comments:[{
_id: "commentId",
userid: "some id of an user" // USER
},{
_id: "commentId2",
userid: "some id of an user2"
}]
}]
}
I want to delete the element from the comments array with the given commentId. This should be done only if userid is USER. If that condition isn't met, that means that comment doesn't belongs to the user that wants to delete it so I decline it.
Tried Attempt :
Post.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(userId)
},
{
$pull: {
$cond: [
{
"posts.$[post].comments.$[comment].userid": {
$eq: USER
}
},
{
$pull: {
comments: {
_id: mongoose.Types.ObjectId(commentId)
}
}
}
]
}
},
{
arrayFilters: [
{
"comment._id": mongoose.Types.ObjectId(commentId)
},
{
"post._id": mongoose.Types.ObjectId(postId)
}
]
}
)
That code above doesn't work, I'm stuck there & I don't know how to continue. maybe somebody knows how to fix this.
You can try below query :
Post.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(userId) // Fetches actual document
},
// Any matching object that has these fields/values in comments array will be pulled out
{
$pull: {"posts.$[post].comments": { _id : mongoose.Types.ObjectId(commentId), "userid": USER }}},
{
arrayFilters: [
{
"post._id": mongoose.Types.ObjectId(postId) // Checks which object inside `posts` array needs to be updated
}
]
}
)
Note : Use an option { new : true } in mongoose to return updated document, or in shell use { returnNewDocument : true }

Renaming Field Within an Array in MongoDB

I need to update the name of field in a collection. The problem is that the field in question is within an array. So I'm trying to determine the correct way do this. I tried this to accomplish renaming the field that exists within the "plans" array: :
db.customers.updateMany( {}, { $rename: { "plans" : { "subscriptionType": "membershipType" } } } );
But this won't work. What's the correct way to handle this kind of transformation of a field within an array?
The data looks like this:
{
_id: 123,
prop1: value,
prop2: value,
prop3: value,
plans: [
subscriptionType: value,
otherProp: value,
otherProp: value
]
}
You can use Aggregation Framework's $addFields to override plans field and $map operator to rename field inside an array. Then you can use $out to override existing collection:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
membershipType: "$$plan.subscriptionType",
otherField: "$$plan.otherField",
otherField2: "$$plan.otherField2"
}
}
}
}
},
{
$out: "customers"
}
])
Alternatively you can do that dynamically. In this solution you don't have to explicitly specify other field names:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
$mergeObjects: [
{ membershipType: "$$plan.subscriptionType" },
{
$arrayToObject: {
$filter: {
input: { $objectToArray: "$$plan" },
as: "plan",
cond: { $ne: [ "$$plan.k", "subscriptionType" ] }
}
}
}
]
}
}
}
}
},
{
$out: "customers"
}
])
Using $objectToArray to $filter out old key-value pair and the using $mergeObjects to combine that filtered object with new renamed field.