Mongodb- Delete some of the array elements within embedded document - mongodb

There are 2 kinds of documents.
Type 1. Documents contain the either MCN-ONE, MCN-TWO, MCN-THREE(or all 3) along with other values
2. Another type of documents do not contain any among these values.
First, I would like to get the documents having those array elements(either 1 or 2 or all 3). Then I want to keep MCN-ONE,MCN-TWO,MCN-THREE and delete all others (CCC-ALARM..etc) in bulk. Could you help to write the query? The below mentioned document falls in type 1.
{
"_id" : ObjectId("5d721f5296eaaafd1df263e8"),
"assetId" : "ALL",
"createdTime" : ISODate("2019-09-06T08:56:50.065Z"),
"default" : false,
"lastUpdatedTime" : ISODate("2019-09-06T09:11:35.463Z"),
"preferences" : {
"MCN-TWO" : [
"TEST"
],
"MCN-ONE" : [
"TEST",
"TEST",
"TEST"
],
"MCN-THREE" : [
"TEST"
],
"CCC-ALARM" : [
"TEST"
],
"SSD-ALARM" : [
"TEST"
],
"TFT-ALARM" : [
"TEST",
"TEST"
],
"REC-WARN" : []
}
}

The generic approach would be to transform preferences subdocument with $objectToArray, then filter desired elements with $filter, $map or $reduce and transform back with $arrayToObject. However, your requirement is "get elements MCN-ONE,MCN-TWO,MCN-THREE". The simple way is to update element preferences and replace just with conten of MCN-ONE,MCN-TWO,MCN-THREE. It can be done by this aggregation:
In order to filter documents, set the $match stage:
db.collection.aggregate(
[
{
$match: {
$expr: {
$or: [
{ $ne: ["$preferences.MCN-ONE", null] },
{ $ne: ["$preferences.MCN-TWO", null] },
{ $ne: ["$preferences.MCN-THREE", null] }
]
}
}
},
{
$set: {
preferences: {
$mergeObjects: [
{ "MCN-ONE": "$preferences.MCN-ONE" },
{ "MCN-TWO": "$preferences.MCN-TWO" },
{ "MCN-THREE": "$preferences.MCN-THREE" }
]
}
}
}
]
)

Related

Count and apply condition to slice the mongodb array document

My document structure looks like this:
{
"_id" : ObjectId("5aeeda07f3a664c55e830a08"),
"profileId" : ObjectId("5ad84c8c0e71892058b6a543"),
"list" : [
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
],
}
I want to count array of
list field. And apply condition before slicing that
if the list<=10 then slice all the elements of list
else 10 elements.
P.S I used this query but is returning null.
db.getCollection('post').aggregate([
{
$match:{
profileId:ObjectId("5ada84c8c0e718s9258b6a543")}
},
{$project:{notifs:{$size:"$list"}}},
{$project:{notifications:
{$cond:[
{$gte:["$notifs",10]},
{$slice:["$list",10]},
{$slice:["$list","$notifs"]}
]}
}}
])
Your first $project stage effectively wipes out all result fields but the one(s) that it explicitly projects (only notifs in your case). That's why the second $project stage cannot $slice the list field anymore (it has been removed by the first $project stage).
Also, I think your $cond/$slice combination can be more elegantly expressed using the $min operator. So there's at least the following two fixes for your problem:
Using $addFields:
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $addFields: { notifs: { $size: "$list" } } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ "$notifs", 10 ] } ]
}
}}
])
Using a calculation inside the $project - this avoids a stage so should be preferable.
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ { $size: "$list" }, 10 ] } ]
}
}}
])

Match Documents based on Nested Array Values and Count Unique

I have a MongoDB Collection which has Documents in Given format,
{
"_id" : ObjectId("595f5661f34ae7b2adee31bc"),
"app_userUpdatedOn" : "2017-03-09T12:01:07.615Z",
"appId" : 31625,
"app_lastCommunicatedAt" : "2017-03-09T12:18:53.067Z",
"currentDate" : "2017-03-09T12:19:28.626Z",
"objectId" : "58c14850e4b0b2406992b29e",
"name" : "APPSESSION",
"action" : "START",
"installationId" : "98088f6641a0fa79",
"userName" : "98088f6641a0fa79",
"properties" : [
[
"userid",
"98088f6641a0fa79"
],
[
"app_os_version",
"6.0.1"
],
[
"app_installAt",
"2017-03-09T12:01:01.307Z"
],
[
"app_model",
"SM-J210F"
],
[
"app_lastCommunicatedAt",
"2017-03-09T12:18:53.067Z"
],
[
"app_carrier",
"Jio 4G"
],
[
"app_counter",
1
],
[
"app_brand",
"samsung"
],
[
"app_lib_version",
"1.0"
],
[
"app_app_version",
"3.0.2"
],
[
"app_os",
"Android"
]
],
"date" : "2017-03-09"
}
{
"_id" : ObjectId("595f5661f34ae7b2adee31bd"),
"app_userUpdatedOn" : "2017-02-05T07:38:32.866Z",
"appId" : 31625,
"app_lastCommunicatedAt" : "2017-03-09T08:09:05.342Z",
"currentDate" : "2017-03-09T12:19:28.806Z",
"objectId" : "58c14850e4b06ec88ecaa9c6",
"name" : "APPINSTALL",
"action" : "START",
"installationId" : "eef436554fbdf4ac",
"userName" : "eef436554fbdf4ac",
"properties" : [
[
"userid",
"eef436554fbdf4ac"
],
[
"app_os_version",
"5.1"
],
[
"app_installAt",
"2017-02-05T11:20:49.809Z"
],
[
"app_model",
"Micromax Q465"
],
[
"app_lastCommunicatedAt",
"2017-03-09T08:09:05.342Z"
],
[
"app_carrier",
"JIO 4G"
],
[
"app_counter",
1
],
[
"app_brand",
"Micromax"
],
[
"app_lib_version",
"1.0"
],
[
"app_app_version",
"3.0.2"
],
[
"app_os",
"Android"
]
],
"date" : "2017-03-09"
}
I want to Fetch the Count and Unique Count of the Documents where currentDate lies in between, startDate and endDate, name is x (eg. APPSESSION), Containing multiple Properties Nested Array (like ["app_installAt","This can be any value instead of null"] ,["app_model","This can be any value instead of null"], and so on... ), Group By userName
Previously i have created a Query in which Nested Array Both Element are Known, and it is as follows
db.testing.aggregate(
[
{$match: {currentDate: {$gte:"2017-03-01T00:00:00.000Z", $lt:"2017-03-02T00:00:00.000Z"},name:"INSTALL"}},
{$match: {properties: ["app_os_version","4.4.2"]}},
{$match: {properties: ["app_carrier","telenor"]}},
{$match: {properties: ["app_brand","Micromax"]}},
{$group: {_id: "$userName"}},
{$count: "uniqueCount"}
]
);
But i am unable to find the Data where i know only 0th index of Property Data Nested Array.
Please do Help.
Thanks in Advance.... :)
The query for this is essentially the use of $all for the multiple conditions to match in the array and then use $elemMatch and $eq to match the individual array elements.
For example to match and count the first document supplied in your question "only" the parameters would be:
db.testing.find({
"currentDate": {
"$gte": "2017-03-09T00:00:00.000Z",
"$lt": "2017-03-10T00:00:00.000Z"
},
"properties": {
"$all": [
{ "$elemMatch": { "$eq": ["app_os_version","6.0.1"] } },
{ "$elemMatch": { "$eq": ["app_carrier", "Jio 4G"] } },
{ "$elemMatch": { "$eq": ["app_brand", "samsung"] } }
]
}
})
With .aggregate() then you put the whole query into a single $match stage as in:
db.testing.aggregate([
{ "$match": {
"currentDate": {
"$gte": "2017-03-09T00:00:00.000Z",
"$lt": "2017-03-10T00:00:00.000Z"
},
"properties": {
"$all": [
{ "$elemMatch": { "$eq": ["app_os_version","6.0.1"] } },
{ "$elemMatch": { "$eq": ["app_carrier", "Jio 4G"] } },
{ "$elemMatch": { "$eq": ["app_brand", "samsung"] } }
]
}
}},
{ "$group": { "_id": "$userName" }
{ "$count": "unique_count"
])
So $elemMatch in this context is going to examine each "inner" array and see if it matches the supplied conditions, which we give in argument as an "array" to the $eq operator.
The wrapping $all means that "all" the provided $elemMatch conditions "must" be met in order to fulfill the query conditions. And that is how the selection gets made with this type of structure.
If you needed to adjust one of those then the "inner" match is using the element of the array. So on the key it would use the "0" for the index position. i.e:
{ "$elemMatch": { "0": "app_os_version" } },

MongoDB - $pull not working on Number array

I have a JSON document like the below one. I would like to remove the matching element from the arrays.
{
"_id" : NumberInt(1),
"fruits" : [
"pears",
"bananas"
],
"vegetables" : [
"carrots",
"celery",
"squash",
"carrots"
],
"ids" : [
NumberLong(2825459592),
NumberLong(328257222163),
NumberLong(825544354),
NumberLong(3282580412308),
NumberLong(28254518083),
NumberLong(32825684682),
NumberLong(3282574078116),
NumberLong(32825709226),
NumberLong(328255745773)
]
}
From String array, The below update works.
db.test.update(
{},
{ $pull: { fruits: { $in: [ "pears" ] }} },
{ multi: true }
)
From Number array, The below update doesnt work. No error and not removing.
db.test.update(
{},
{ $pull: { ids: { $in: [ 28254518083 ] }} },
{ multi: true }
)
I am using MongoChef's IntelliShell for executing the above commands.
This was a known issue, fixed in Mongo 3.2.10. I am able to reproduce the issue on 3.2.9, but not on 3.2.14. Refer here

mongodb aggregate nested nested array

how i can find the document with $match on position 3 (only last item in array "ndr"). It is necessary that the aggreation search only in the last array-item of ndr.
{
"_id" : ObjectId("58bd5c63a3d24b4a2e4cde03"),
"name" : "great document",
"country" : "us_us",
"cdate" : ISODate("2017-03-06T12:56:03.405Z"),
"nodes" : [
{
"node" : 3244343,
"name" : "Best Node ever",
"ndr" : [
{
"position" : 7,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
},
{
"position" : 3,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
}
]
}
],
}
I need this result after aggregation
{
"name" : "great document",
"country" : "us_us",
"cdate" : ISODate("2017-03-06T12:56:03.405Z"),
"nodes" : [
{
"node" : 3244343,
"name" : "Best Node ever",
"ndr" : [
{
"position" : 3,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
}
]
}
]
}
I hope anyone can help me.
You can try below aggregation with Mongo 3.4 version.
The below query finds the last item (-1) using $arrayElemAt operator in the ndr array and stores the variable in last using $let operator for each nodes and compare the last variable position value using $$ notation to 3 and wraps the nbr element within array [] if entry found and else returns empty array.
$map operator to reach nbr array inside the nodes array and project the updated nbr array while mapping the rest of nodes fields.
$addFields stage will overwrite the existing nodes with new nodes while keeping the all the other fields.
db.collection.aggregate([{
$addFields: {
nodes: {
$map: {
input: "$nodes",
as: "value",
in: {
node: "$$value.node",
name: "$$value.name",
ndr: {
$let: {
vars: {
last: {
$arrayElemAt: ["$$value.ndr", -1]
}
},
in: {
$cond: [{
$eq: ["$$last.position", 3]
},
["$$last"],
[]
]
}
}
}
}
}
}
}
}]);
Update:
You can try $redact which will keep the whole document if it finds the matching position from with the given filter.
$map to project the true, false values based on the filter for each of the nodes nbr position value and $anyElementTrue will inspect the previous boolean values for each doc and return a true or false value and $redact will use the booelan value from above comparison; true value to keep and false value to remove the document.
db.collection.aggregate([{
$redact: {
$cond: [{
$anyElementTrue: {
$map: {
input: "$nodes",
as: "value",
in: {
$let: {
vars: {
last: {
$arrayElemAt: ["$$value.ndr", -1]
}
},
in: {
$cond: [{
$eq: ["$$last.position", 3]
},
true,
false
]
}
}
}
}
}
}, "$$KEEP", "$$PRUNE"]
}
}]);
you will need to unwind both nested arrays.
db.<collection>.aggregate([
{ $unwind: '$nodes' },
{ $unwind: '$nodes.ndr'},
{ $group: {'_id':{'_id':'$_id', 'nodeID', '$nodes.node' },
'name':{'$last':'$name'},
'country':{'$last':'$country'},
'cdate':{'$last':'$cdate'},
'nodes':{'$last':'$nodes'}
}
},
{ $match : { nodes.ndr.position: 3 } }
]);
From here you can reassemble the aggregate results with a $group on the and do a projection. I'm not sure what your ultimate end result should be.

Find MongoDB object using value of another field

I recently found difficulty in finding an object stored in a document with its key in another field of that same document.
{
list : {
"red" : 397n8,
"blue" : j3847,
"pink" : 8nc48,
"green" : 983c4,
},
result : [
{ "id" : 397n8, value : "anger" },
{ "id" : j3847, value : "water" },
{ "id" : 8nc48, value : "girl" },
{ "id" : 983c4, value : "evil" }
]
}
}
I am trying to get the value for 'blue' which has an id of 'j3847' and a value of 'water'.
db.docs.find( { result.id : list.blue }, { result.value : 1 } );
# list.blue would return water
# list.pink would return girl
# list.green would return evil
I tried many things and even found a great article on how to update a value using a value in the same document.: Update MongoDB field using value of another field which I based myself on; with no success... :/
How can I find a MongoDB object using value of another field ?
You can do it with the $filter operator within mongo aggregation. It returns an array with only those elements that match the condition:
db.docs.aggregate([
{
$project: {
result: {
$filter: {
input: "$result",
as:"item",
cond: { $eq: ["$list.blue", "$$item.id"]}
}
}
}
}
])
Output for this query looks like this:
{
"_id" : ObjectId("569415c8299692ceedf86573"),
"result" : [ { "id" : "j3847", "value" : "water" } ]
}
One way is using the $where operator though would not recommend as using it invokes a full collection scan regardless of what other conditions could possibly use an index selection and also invokes the JavaScript interpreter over each result document, which is going to be considerably slower than native code.
That being said, use the alternative .aggregate() method for this type of comparison instead which is definitely the better option:
db.docs.aggregate([
{ "$unwind": "$result" },
{
"$project": {
"result": 1,
"same": { "$eq": [ "$list.blue", "$result.id" ] }
}
},
{ "$match": { "same": true } },
{
"$project": {
"_id": 0,
"value": "$result.value"
}
}
])
When the $unwind operator is applied on the result array field, it will generate a new record for each and every element of the result field on which unwind is applied. It basically flattens the data and then in the subsequent $project step inspect each member of the array to compare if the two fields are the same.
Sample Output
{
"result" : [
{
"value" : "water"
}
],
"ok" : 1
}
Another alternative is to use the $map and $setDifference operators in a single $project step where you can avoid the use of $unwind which can be costly on very large collections and in most cases result in the 16MB BSON limit constraint:
db.docs.aggregate([
{
"$project": {
"result": {
"$setDifference": [
{
"$map": {
"input": "$result",
"as": "r",
"in": {
"$cond": [
{ "$eq": [ "$$r.id", "$list.blue" ] },
"$$r",
false
]
}
}
},
[false]
]
}
}
}
])
Sample Output
{
"result" : [
{
"_id" : ObjectId("569412e5a51a6656962af1c7"),
"result" : [
{
"id" : "j3847",
"value" : "water"
}
]
}
],
"ok" : 1
}