how to update specific object in array which match conditions - mongodb

actually i want to update specific object in my documents array which has viewProfile : true but it's gonna update all elements in array
here is my query:
const user = await User.updateMany(
{
_id: req.userData.id,
"notifications.viewProfile" : true
},
{
$set: {
"notifications.$[].read": false
}
}
);
consider this document
/* 1 */
{
"_id" : ObjectId("5e4ae76bd4e24418a020b665"),
"username" : "test",
"notifications" : [
{
"createdAt" : ISODate("2020-02-18T19:22:24.809Z"),
"_id" : ObjectId("5e4c397075265a36203dab5a"),
"message" : "testone is now following you.",
"viewProfile" : false,
"read" : false
},
{
"createdAt" : ISODate("2020-02-18T19:22:29.200Z"),
"_id" : ObjectId("5e4c397575265a36203dab5d"),
"message" : "testone is now following you.",
"viewProfile" : true,
"read" : false
}
],
}

Use filtered positional operator $[<identifier>] . Reference https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/
const user = await User.update(
{
_id: req.userData.id
},
{ $set: { "notifications.$[element].read": false } },
{
multi: true,
arrayFilters: [ { "element.viewProfile": true } ]
}
)

Related

Get documents only if all elements inside an array match the condition in MongoDb

I am trying to delete all orphan projects. In db I need to get the projects where all of its users are deleted. Already tried with below query but it's always giving the results where the delete field is absent.
db.projects.find({
"user_list": {
$not: {
$elemMatch: {
"deleted": false
}
}
},
"user_list.deleted": {$exists: true}
}, {"user_list": 1})
Need help in writing a query to fetch the only projects with all user deleted. E.g in below example I should only get the second document.
List of projects
/* 1 */
{
"_id" : ObjectId("636a6aa584d5f92f14f0c548"),
"user_list" : [
{
"deleted" : false,
"user_id" : "602cf72a3fcad3cc605b8d59"
},
{
"deleted" : true,
"user_id" : "602cf72a3fcad3cc605b8d50"
}
]
}
/* 2 */
{
"_id" : ObjectId("602e443bacdd4184511d6e29"),
"user_list" : [
{
"deleted" : true,
"user_id" : "602cf72a3fcad3cc605b8d59"
},
{
"deleted" : true,
"user_id" : "602cf72a3fcad3cc605b8d59"
},
{
"deleted" : true,
"user_id" : "602cf72a3fcad3cc605b8d59"
}
]
}
/* 3 */
{
"_id" : ObjectId("60332242acdd4184511ed664"),
"user_list" : [
{
"deleted" : true,
"user_id" : "602cf72a3fcad3cc605b8d59",
},
{
"deleted" : true,
"user_id" : "602cf72a3fcad3cc605b8d59"
},
{
"user_id" : "602cf72a3fcad3cc605b8d59"
}
]
}
You can use $nin to filter out false and null values.
db.projects.find({
"user_list.deleted": {
$nin: [
false,
null
]
}
})
MongoDB Playground
db.projects.find({ $and: [
{ "user_list" : { $in : ["deleted"] } },
{ "user_list" : { $elemMatch : { "deleted" : true} } },
{ "user_list" : { $elemMatch : { "deleted" :{$ne:false}} } } ]});
Try with this query. I think it should work in all mentioned cases.
db.projects.find(
{ "user_list.deleted": { $ne: false } }
)
should give you all the projects which has no record of deleted === false.
you can $and it with $ne: null for null set to deleted to avoid them too.
Updates:
From https://docs.mongodb.com/manual/reference/operator/query/and/index.html
I could imagine following to work, but is not valid when I run it on my local machine. This could be because of the nesting.
{ user_list.deleted: { $ne: false, $exists: true } }
But this one works,
{ user_list.deleted: { $ne: false, $ne: null } }

MongoDb: Match value on foreign collection

I have the collection USER:
{
"_id" : ObjectId("5d64d2bf48dd17387d77d27a"),
"name" : "John",
"notifications" : {
"email" : false,
"sms" : true
}
},
{
"_id" : ObjectId("5da9586911e192081ee1c6be"),
"name" : "Mary",
"notifications" : {
"email" : false,
"sms" : false
}
}
And this other collection ALERT:
{
"_id" : ObjectId("5d54f04dbe5e6275e53a551e"),
"active" : true,
"user_id" : ObjectId("5d64d2bf48dd17387d77d27a")
},
{
"_id" : ObjectId("5d54f04dbe5e6275e53a551f"),
"active" : false,
"user_id" : ObjectId("5d64d2bf48dd17387d77d27a")
},
{
"_id" : ObjectId("5d54f04dbe5e6275e53a552e"),
"active" : true,
"user_id" : ObjectId("5da9586911e192081ee1c6be")
},
{
"_id" : ObjectId("5d54f04dbe5e6275e53a552f"),
"active" : true,
"user_id" : ObjectId("5da9586911e192081ee1c6be")
}
I want a MongoDB query that lists the documents on collection ALERT that have property "active" as TRUE and whose matching USER has element "sms" on property "notifications" as TRUE too.
You can use Uncorelated sub queries in $lookup
$match to get the "notifications.sms": true
$lookupto join two collections. We are assigning uId = _id from USER collection. Inside the pipeline, we use $match to find the active :true, and _id=uId
here is the script
db.USER.aggregate([
{
"$match": {
"notifications.sms": true
}
},
{
"$lookup": {
"from": "ALERT",
"let": {
uId: "$_id"
},
"pipeline": [
{
$match: {
$and: [
{
active: true
},
{
$expr: {
$eq: [
"$user_id",
"$$uId"
]
}
}
]
}
}
],
"as": "joinAlert"
}
}
])
Working Mongo playground

How can I correctly output an array of objects in the reverse order from mongodb?

Comments are saved in an array of objects. How can I correctly output them in reverse order (comments from newest to oldest)?
My db:
{"_id":{"$oid":"5e3032f14b82d14604e7cfb7"},
"videoId":"zX6bZbsZ5sU",
"message":[
{"_id":{"$oid":"5e3032f14b82d14604e7cfb8"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"1"
},
{"_id":{"$oid":"5e3032f14b82d14604e7cfb9"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"2"
},
....
]
My sheme Mongoose:
const schema = new Schema({
videoId: { type: String, isRequired: true },
message: [
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: { type: String }
},
]
});
My code:
const userComments = await Comment.find(
{ videoId: req.query.videoId },
{ message: { $slice: [skip * SIZE_COMMENT, SIZE_COMMENT] } }
)
.sort({ message: -1 })
.populate('message.user', ['avatar', 'firstName']);
but sort not working;
thanks in advance!
You can simply use $reverseArray to reverse content of an array.
db.collection.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}
])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
}
]
}
Result :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
}
]
}
Your Query : You can use native MongoDB's $lookup instead of .populate() , So try below :
Comments.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}, {
$lookup: {
from: "User",
let: { ids: "$message.user" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$ids"] } }
},
{ $project: { avatar: 1, firstName: 1, _id: 0 } }
],
as: "userData"
}
}
])
You're going to want to use one of two things:
The MongoDB aggregation framework
Sorting within the application
If you choose to use the MongoDB aggregation framework, you'll likely want to use the $unwind operation to expand the array into separate documents, then sorting those documents with the $sort operation.
You can see more on how to do that in this ticket:
how to sort array inside collection record in mongoDB
You could also do this within your application by first executing a query, and then sorting each of the arrays in the result set.
Best,

Update nested array element in mongodb

In my case I have mongodb document with nested arrays and I need to update attributes array elements. My mongodb document as follows.
{
"_id" : ObjectId("5dca9c5ece5b91119746eece"),
"updatedAt" : ISODate("2019-11-20T11:48:55.339Z"),
"attributeSet" : [
{
"attributeSetName" : "test-0",
"type" : "Product",
"id" : "1574158032603",
"updatedAt" : ISODate("2019-11-19T10:10:53.783Z"),
"createdAt" : ISODate("2019-11-19T10:07:20.084Z"),
"attributes" : [
{
"attributeName" : "test-attribute",
"defaultValue" : 123,
"isRequired" : false,
"id" : "1574221129398"
},
{
"attributeName" : "test-attribute-02",
"defaultValue" : 456,
"isRequired" : false,
"id" : "1574250533840"
}
]
},
{
"attributeSetName" : "test-1",
"type" : "Product",
"id" : "1574158116355",
"updatedAt" : ISODate("2019-11-19T10:08:37.251Z"),
"createdAt" : ISODate("2019-11-19T10:08:37.251Z"),
"attributes" : []
}
]
}
I need to update element in attributes array and get the updates document into result object. This is what I tried so far.
const result = await this.model.findOneAndUpdate(
{
_id: settingsToBeUpdated._id,
"attributeSet": {
$elemMatch: {
"attributeSet.id": attributeSetId,
"attributes": {
$elemMatch: {
'attributes.id': id
}
}
}
}
},
{
$set: {
'attributeSet.$[outer].attributes.$[inner].attributeName': attributeDto.attributeName,
'attributeSet.$[outer].attributes.$[inner].defaultValue': attributeDto.defaultValue,
'attributeSet.$[outer].attributes.$[inner].isRequired': attributeDto.isRequired,
}
},
{
"arrayFilters": [
{ "outer.id": attributeSetId },
{ "inner.id": id }
]
}
);
It does not update the model. I refered to this link, but it does not help.
Any suggestion would be appreciated.
Couple of rectification required on the query, otherwise its almost there. The update is not working because $elemMatch for attributeSet (array of docs) field is to happen on id property of those docs to filter and not on attributeSet.id, it woudn't figure what it is. And nested elemMatch is not required, simply use dot notation.
To debug you can try it out with a find query.
Query (Shell):
db.collection.findOneAndUpdate(
{
_id: settingsToBeUpdated._id,
attributeSet: {
$elemMatch: {
id: attributeSetId,
"attributes.id": id
}
}
},
{
$set: {
"attributeSet.$[as].attributes.$[a].attributeName":
attributeDto.attributeName,
"attributeSet.$[as].attributes.$[a].defaultValue":
attributeDto.defaultValue,
"attributeSet.$[as].attributes.$[a].isRequired": attributeDto.isRequired
}
},
{
arrayFilters: [{ "as.id": attributeSetId }, { "a.id": id }],
returnNewDocument: true
}
);

Mongodb : How should I get original Json structure after filter the records based on requirement?

I am new to mongodb.
I have a Json document in collection like :
{
"_id" : ObjectId("55abf32f358e3aca807f0e6a"),
"usercbid" : 1995492.0000000000000000,
"defaultnotifytype" : {
"status" : true,
"alert" : true,
"action" : true
},
"calendar" : {
"alert" : 2468.0000000000000000,
"action" : 13579.0000000000000000,
"status" : 123456.0000000000000000
},
"assignment" : [
{
"orgid" : {
"service" : "AVPN",
"adminemail" : "pl9129#att.com",
"notifytype" : {
"status" : true,
"alert" : true
},
"keytype" : "MCN",
"KeyValue" : "SK1383"
}
},
{
"orgid" : {
"KeyValue" : "DD3342",
"service" : "<all>",
"keytype" : "MCN"
}
},
{
"orgid" : {
"notifytype" : {
"optout" : true
},
"keytype" : "MCN",
"keyvalue" : "<all>",
"service" : "MVPN"
}
},
{
"order" : {
"date" : "2015-03-15",
"adminemail" : "abc.com",
"notifytype" : {
"alert" : true
},
"id" : 123456.0000000000000000
}
},
{
"order" : {
"id" : 135246.0000000000000000,
"date" : "2015-03-17",
"adminemail" : "abc.com"
}
}
]
}
I would like to filter above json document with following condition:
var result = db.subscription.aggregate(
[ { $unwind: "$assignment" }
, {$match : {$or:
[
{
"assignment.order.id" : 123456
},
{
"assignment.orgid.keytype" : { $in: ["MCN"]}
,"assignment.orgid.KeyValue" : { $in: ["<all>","SK1383"]}
,"assignment.orgid.service" : { $in: ["<all>","AVPN"]}
}
]
}
}
,{$group: {_id: "$_id", assignment: {$push: "$assignment"}}}
// ,{$project : { usercbid : $usercbid, defaultnotifytype : 1, calendar : 1, assignment: 1} }
]
)
printjson(result);
Result of above query is :
{
"result" : [
{
"_id" : ObjectId("55abf32f358e3aca807f0e6a"),
"assignment" : [
{
"orgid" : {
"service" : "AVPN",
"adminemail" : "pl9129#att.com",
"notifytype" : {
"status" : true,
"alert" : true
},
"keytype" : "MCN",
"KeyValue" : "SK1383"
}
},
{
"order" : {
"date" : "2015-03-15",
"adminemail" : "pl9129#att.com",
"notifytype" : {
"alert" : true
},
"id" : 123456
}
}
]
}
],
"ok" : 1
}
But my final result lost the following original content:
"usercbid" : 1995492.0000000000000000,
"defaultnotifytype" : {
"status" : true,
"alert" : true,
"action" : true
},
"calendar" : {
"alert" : 2468.0000000000000000,
"action" : 13579.0000000000000000,
"status" : 123456.0000000000000000
},
How should I append above original content with filtered records?
Thanks,
$Fisrt is the operator which helps you getting the required output.
When you do a $Group, the result of the $Group pipeline operator contains only those fields which are specified inside the $Group pipeline operator.
So, from your query we can notice that you are grouping based on "_Id" and you are selecting only "assignment" key field, so the OUTPUT of this group pipeline operator will contain only those 2 fileds ( "_ID" and "assignment" ).
To make sure that the other left out feilds ( usercbid, defaultnotifytype , calendar ) to be part of the $Group pipeline output, we need to mention that explicitly in the Group pipeline using $First as below :
{ $group: { _id: "$_id", assignment: {$push: "$assignment"},
usercbid : { $first : "usercbid"} ,
defaultnotifytype : { $first : "defaultnotifytype" } ,
calendar : { $first : "calendar"}
}
}
$First Returns the value that results from applying an expression to the first document in a group of documents that share the same group by key.
Please check the below query, it will help you in fetching the required output :
var result = db.subscription.aggregate(
[ { $unwind: "$assignment" }
, { $match : {$or:
[
{
"assignment.order.id" : 123456
},
{
"assignment.orgid.keytype" : { $in: ["MCN"]}
,"assignment.orgid.KeyValue" : { $in: ["<all>","SK1383"]}
,"assignment.orgid.service" : { $in: ["<all>","AVPN"]}
}
]
}
}
,{ $group: { _id: "$_id", assignment: {$push: "$assignment"},
usercbid : { $first : "usercbid"} ,
defaultnotifytype : { $first : "defaultnotifytype" } ,
calendar : { $first : "calendar"}
}
}
]
).pretty();