How to add or remove a element in double nested array? - node.js

Example of the document:
{
postId:'232323',
post:'This is my first post',
commentsOnPost:[
{
commentId:'232323_8888',
comment:'Congrats',
repliesOnPost:[
{
replyId:'232323_8888_66666',
reply:'Thanks',
likesOnReply:['user1','user5','user3'],
}
]
}
]
}
I want to add userid in likesOnReply if users do not exist in likesOnReply, similarly remove userid from likesOnReply if exist.
I have tried like this but not working properly
await collection('post').findOneAndUpdate(
{
postId: postId,
'commentsOnPost.commentId': commentId,
'commentsOnPost.repliesOnPost.replyId': replyId
},
{
$push: { 'commentsOnPost.$[].repliesOnPost.$.likes': userid },
},
);

There is no straight way to do both the operation to pull or push in a single query,
There are 2 approaches,
1) Find and update using 2 queries:
use arrayFilters to updated nested array elements
$push to insert element
$pull to remove element
var post = await collection('post').findOne({
posted: postId,
ommentsOnPost: {
$elemMatch: {
commentId: commentId,
repliesOnPost: {
$elemMatch: {
replyId: replyId
likesOnReply: userid
}
}
}
}
});
var updateOperator = "$push";
// FOUND USER ID THEN DO REMOVE OPERATION
if (post) updateOperator = "$pull";
// QUERY
await collection('post').updateOne(
{ postId: postId },
{
[updateOperator]: {
"commentsOnPost.$[c].repliesOnPost.$[r].likesOnReply": userid
}
},
{
arrayFilters: [
{ "c.commentId": commentId },
{ "r.replyId": replyId }
]
}
)
Playground
2) Update with aggregation pipeline starting from MongoDB 4.2:
$map to iterate loop of commentsOnPost array check condition if commentId match then go to next process otherwise return existing object
$mergeObjects to merge current object with updated fields
$map to iterate loop of repliesOnPost array and check condition if replyId match then go to next process otherwise return an existing object
check condition for likesOnReply has userid then do remove using $filter otherwise insert using $concatArrays
await collection('post').findOneAndUpdate(
{ postId: "232323" },
[{
$set: {
commentsOnPost: {
$map: {
input: "$commentsOnPost",
in: {
$cond: [
{ $eq: ["$$this.commentId", commentId] },
{
$mergeObjects: [
"$$this",
{
repliesOnPost: {
$map: {
input: "$$this.repliesOnPost",
in: {
$cond: [
{ $eq: ["$$this.replyId", replyId] },
{
$mergeObjects: [
"$$this",
{
likesOnReply: {
$cond: [
{ $in: [userid, "$$this.likesOnReply"] },
{
$filter: {
input: "$$this.likesOnReply",
cond: { $ne: ["$$this", userid] }
}
},
{
$concatArrays: ["$$this.likesOnReply", [userid]]
}
]
}
}
]
},
"$$this"
]
}
}
}
}
]
},
"$$this"
]
}
}
}
}
}]
)
Playgorund

Related

MongoDB: pullAll / pull whole nested array object by value using mongoose

I want to pull the whole nested array object if the object contains a specified string for a specific key. I'm using mongoose with nodejs:
DB before deletion:
{ _id : 1234
fallBackData: {
nestedKey: [ { arrayKey: "theValue" }, { arrayKey: "anotherValue" } ]
}
}
DB after deletion:
{ _id : 1234
fallBackData: {
nestedKey: [ { arrayKey: "anotherValue" } ]
}
}
I took a look at How can I pull nested object value in mongodb and $pullAll Mongo DB docs ,tried the following, but none worked:
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
fallbackData: { nestedKey: [{ arrayKey: "theValue"}] },
},
}
);
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
"fallbackData.$.nestedKey" : { arrayKey: "theValue" },
},
}
);
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
"fallbackData.$.nestedKey" : [{ arrayKey: "theValue"}],
},
}
);
The query return value is the following, but the object in the array is not deleted:
{
acknowledged: true,
modifiedCount: 1,
upsertedId: null,
upsertedCount: 0,
matchedCount: 1
}
You can achieve this by changing a little
playground
db.collection.update({
_id: 1234,
"fallBackData.nestedKey": {
$elemMatch: {
"arrayKey": "theValue"
}
}
},
{
"$unset": {
"fallBackData": "nestedKey"
}
})
You cannot add matching conditions with $pullAll related to Array
$pullAll expects an array of matching values to be removed
You can do $pull instead of $unset but $pull results empty array
The answer from #Gibbs removes the array completely, while you asked only to pull the specific object from it.
Here is the simple solution with $pull command:
db.collection.update({
_id: 1234,
"fallBackData.nestedKey": {
$elemMatch: {
"arrayKey": "theValue"
}
}
},
{
$pull: {
"fallBackData.nestedKey": {
arrayKey: "theValue"
}
}
})
Mongo Playground link
pullAll matches the entire object (or objects) to be pulled. In other words, if the input was:
[
{
_id: 1234,
fallBackData: {
nestedKey: [
{
arrayKey: "theValue",
foo: "bar"
},
{
arrayKey: "anotherValue",
foo: "baz"
}
]
}
}
]
Then you need to do:
db.collection.update({
_id: 1234
},
{
$pullAll: {
"fallBackData.nestedKey": [
{
arrayKey: "theValue",
foo: "bar"
}
]
}
})
See https://mongoplayground.net/p/iJkfqIWK0JO.
On the other hand, $pull can match objects based on a condition, so you can pull from the array based on the specific key you want to match. So, given the same input as above, you would simply do:
db.collection.update({
_id: 1234
},
{
$pull: {
"fallBackData.nestedKey": {
arrayKey: "theValue"
}
}
})
See https://mongoplayground.net/p/MOuSmh7Ir7b.
The conditions can be more complex than a simple field value match. For example to match and pull multiple keys:
db.collection.update({
_id: 1234
},
{
$pull: {
"fallBackData.nestedKey": {
"arrayKey": {
$in: [
"theValue",
"anotherValue"
]
}
}
}
})
See https://mongoplayground.net/p/iSMVxp7a9TX.

Filtering MongoDB document keys

I am using Mongo 5.0.6 and have a document structured like this:
[
{
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
I need to query data based on the properties keys, this works fine in my mongo playground: https://mongoplayground.net/p/lEiLeStWGTn when I query for key that contain mark.
db.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
])
However when I use it through my mongoose object I get [] even so the data is exactly as in playground. I am using locally the latest mongoose6 and mondodb5.0.6. I get the same [] result when I run the query against a mongodb.com hosted database. What could be the problem?
My javascript query below, I tried both using mongoose and the driver directly, as shown below:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
]);
for await (const doc of data) {
console.log(doc);
}
Always gives me:
{ properties: {} }
When I take out the $addFields and $project like this:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
}
]);
for await (const doc of data) {
console.log(doc);
}
I get, so the data is there, but aggregation pipeline isn't working:
[
{
_id: "61b9f2f5d2a6021365aae6d6",
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
So the data is there. What am I missing? Do I need to write the query differently?

Update last element of a nested array in mongodb

I have tried the below query of mongoose which does not seem to work:
Model.findOneAndUpdate(
{
name: origin
},
{
$set: {
'field1.$[id1].field2.-1': "value"
}
},
{
arrayFilters: [
{ 'id1.userId': "customerId" }
],
new: true
}
);
Note: field1 and field2 are arrays
The negative indexes are not accepted by MongoDB which is causing problems.
You may consider using the $set (aggregation) operator and use double $map operator:
db.collection.aggregate([
{ $match: { name: "myname" } }
{
$set: {
field1: {
$map: {
input: "$field1",
in: {
$cond: {
if: { $ne: [ "$$this.userId", "customerId" ] },
then: "$$this",
else: {
$mergeObjects: [
"$$this",
{
field2: {
$concatArrays: [
{ $slice: [ "$$this.field2", { $add: [ { $size: "$$this.field2" }, -1 ] } ] },
["value"]
]
}
}
]
}
}
}
}
}
}
}
])
Mongo Playground
Apply the $set operator together with the $ positional operator in your update to change the name field.
The $ positional operator will identify the correct element in the array to update without explicitly specifying the position of the element in the array, thus your final update statement should look like:
db.collection.update(
{ "friends.u.username": "michael" },
{ "$set": { "friends.$.u.name": "hello" } }
)
Answer taken from - https://stackoverflow.com/a/34431571

How do I get an object that is inside another with nodejs and mongodb?

I need to obtain the information contained in the array named detalleInsumos through the _id of that object.
I have tried many ways, but I still have not found the solution to my problem, since it always shows me all the objects of that document, which does not work for me.
This is the document from which I need to get that information:
{
"_id":{"$oid":"5f9041196462be3c5ca1e53d"},
"codigoFinca":"000",
"nombreFinca":"PROVINCIANA",
"fechaRegistro":"2020-10-21",
"semanaRegistro":"43",
"usuarioRegistro":"cotorreo",
"trabajadoresFinca":[
{
"porcentajeRecargo":0,
"_id":{"$oid":"5f9041196462be3c5ca1e53e"},
"udpTrabajador":[
{
"unidadesAPagar":null,
"valorUnidad":"",
"areaLaborada":"2",
"semanaNormal":null,
"semanaAtrazos":null,
"_id":{"$oid":"5f9041196462be3c5ca1e53f"},
"detalleInsumos":[
{"_id":{"$oid":"5f9041196462be3c5ca1e540"},
"codigoInsumo":"20000001",
"descripcionInsumo":"NYLON X 5 KILOS",
"cantidadAplicada":"153",
"idRDI":"426715",
"idDetalleSaldo":"24070"
}
],
"codigoLabor":"101",
"nombreLabor":"AMARRE",
"loteLaboro":"1"
}
],
"codigoTrabajador":"0000",
"nombresTrabajador":"HUMBERTO MENA MOSQUERA",
"horasJornada":"10",
"horasLaboradas":"10"
}
],
"createdAt":{"$date":"2020-10-21T14:09:29.876Z"},
"updatedAt":{"$date":"2020-10-21T15:09:51.657Z"},
"__v":0
}
And this is what I have tried from nodejs:
const consultauno = await Modelo.findOne({
'trabajadoresFinca.udpTrabajador.detalleInsumos._id': new ObjectId(idInsumo)
},
{
"trabajadoresFinca.udpTrabajador.detalleInsumos": 1
});
console.log(consultauno);
You can try,
$match your condition
$reduce to iterate loop of trabajadoresFinca array, second $reduce to iterate loop of udpTrabajador array, $filter to get matching object from detalleInsumos array,
$arrayElemAt will get first object from array when condition match
$mergeObjects will merge initialValue or reduce and matching object
const consultauno = await Modelo.aggregate([
{
$match: {
"trabajadoresFinca.udpTrabajador.detalleInsumos._id": ObjectId(idInsumo)
}
},
{
$project: {
detalleInsumos: {
$reduce: {
input: "$trabajadoresFinca",
initialValue: {},
in: {
$mergeObjects: [
"$$value",
{
$reduce: {
input: "$$this.udpTrabajador",
initialValue: {},
in: {
$mergeObjects: [
"$$value",
{
$arrayElemAt: [
{
$filter: {
input: "$$this.detalleInsumos",
cond: {
$eq: ["$$this._id", ObjectId(idInsumo)]
}
}
},
0
]
}
]
}
}
}
]
}
}
}
}
}
])
Playground
Second approach, you can use $unwind,
$match your conditions
$unwind deconstruct trabajadoresFinca array
$unwind deconstruct udpTrabajador array
$unwind deconstruct detalleInsumos array
$match your conditions
$project to show required fields
const consultauno = await Modelo.aggregate([
{
$match: {
"trabajadoresFinca.udpTrabajador.detalleInsumos._id": ObjectId(idInsumo)
}
},
{ $unwind: "$trabajadoresFinca" },
{ $unwind: "$trabajadoresFinca.udpTrabajador" },
{ $unwind: "$trabajadoresFinca.udpTrabajador.detalleInsumos" },
{
$match: {
"trabajadoresFinca.udpTrabajador.detalleInsumos._id": ObjectId(idInsumo)
}
},
{
$project: {
trabajadoresFinca: "$trabajadoresFinca._id",
udpTrabajador: "$trabajadoresFinca.udpTrabajador._id",
detalleInsumos: "$trabajadoresFinca.udpTrabajador.detalleInsumos"
}
}
])
Playground

Mongoose modify single field from .find() response

I'm trying to modify the result of a single field from .find() to remove unnecessary data.
The field likedBy (array) should return an empty array when it doesn't contain the userId. But, when likedBy does contain the userId, it should return the array with only that userId, instead of all userIds.
const response = await MyObject.find().lean({
"likedBy": {
"$elemMatch": {
"$eq": body.userId
}
},
});
Current response when userId = 'id-1':
{
"_id": "some id",
"...rest of the fields"
"likedBy": [
"id-1",
"id-2",
"id-3",
]
},
What I want:
{
"_id": "some id",
"...rest of the fields"
"likedBy": [
"id-1",
]
},
You could define an aggregate pipeline and use $filter to only return the matched array element:
YourModel.aggregate([
{
$match: {
"likedBy": body.userId
}
},
{
$project: {
likedBy: {
$filter: {
input: "$likedBy",
as: "likedBy",
cond: {
$eq: [
"$$likedBy",
body.userId
]
}
}
},
otherField: 1
}
}
])
Here's an example on mongoplayground.

Resources