MongoDB - $pull not working on Number array - mongodb

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

Related

On MongoDb Aggregation lookup, does the let need special formating?

I am trying to use the MongoDB $lookup with the Uncorrelated Subqueries.
Using MongoDB 3.6.12 (support began on 3.6)
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
The following pipeline step is working, however if I swap out the first "userB" with the second, no results are returned.
{
from: 'friendships',
let: { requestuser: ObjectId("5c0a9c37b2365a002367df79"), postuser: ObjectId("5c0820ea17a69b00231627be") },
pipeline: [
{
$match : {
$or : [
{
"userA" : ObjectId("5c0820ea17a69b00231627be"),
"userB" : ObjectId("5c0a9c37b2365a002367df79")
// "userB" : '$$requestuser'
},
{
"userA" : ObjectId("5c0a9c37b2365a002367df79"),
"userB" : ObjectId("5c0820ea17a69b00231627be")
}
],
"accepted" : true
}
},
{
$project: {v: 'true'}
}
],
as: "match"}
Results with hard coded ObjectId:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : "true"
}
]
Results using variable:
"match" : [
]
I feel like ObjectIds need special treatment. All the examples I could find are using simple variables like strings.
To verify the '$$requestUser' contained a value, I tested it on the projection:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : ObjectId("5c0a9c37b2365a002367df79")
}
]
When you use un co-related sub queires, you need to use $expr to pass a variable.
You can try something like following.
{
$match: {
$expr: {
$or: [
{
$and:[
{
$eq: [ "userA", ObjectId("5c0820ea17a69b00231627be") ]
},
{
$eq: [ "userB", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", "$$requestuser" ]
}
]
},
{
$and:[
{
$eq: [ "userA", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", ObjectId("5c0820ea17a69b00231627be") ]
}
]
}
]
},
"accepted": true,
}
}
I have created a sample demo to show how $expr works inside the lookup : Sample demo for Uncorrelated Subquery

Mongodb- Delete some of the array elements within embedded document

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" }
]
}
}
}
]
)

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" } },

Match docs having all array element within $gte $lte

Here are my example documents:
{
updated: [
1461062102,
1461062316
],
name: "test1",
etc: "etc"
}
{
updated: [
1460965492,
1461060275
],
name: "test2",
etc: "etc"
}
{
updated: [
1461084505
],
name: "test3",
etc: "etc"
}
{
updated: [
1461060430
],
name: "test4",
etc: "etc"
}
{
updated: [
1460965715,
1461060998
],
name: "test5",
etc: "etc"
}
What is the correct usage of find query to fetch all documents matching updated date within $gte and $lte criteria?
for example
db.test.find({'updated':{$elemMatch:{$gte:1461013201,$lte:1461099599}}})
I can use $or and set it it like updated.0:{$gte:1461013201,$lte:1461099599}, update.1:{$gte:1461013201,$lte:1461099599} etc but what if my array will contain more updated dates?
As I understand $elemMatch doesnt' fit my criteria because it only matches the first occurence in array.
Good question. You were on the right track with $elemMatch, but this does take other logic not covered in standard operators.
So you either do with $redact:
db.test.aggregate([
{ "$match": {
'updated': { '$elemMatch':{ '$gte':1461013201, '$lte':1461099599 } }
}},
{ "$redact": {
"$cond": {
"if": {
"$allElementsTrue": {
"$map": {
"input": "$updated",
"as": "upd",
"in": {
"$and": [
{ "$gte": [ "$$upd", 1461013201 ] },
{ "$lte": [ "$$upd", 1461099599 ] }
]
}
}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Or in versions earlier than MongoDB 2.6, you handle with a $where clause:
db.test.find({
'updated': { '$elemMatch':{ '$gte':1461013201, '$lte':1461099599 } },
"$where": function() {
return this.updated.filter(function(el) {
return el >= 1461013201 && el <= 1461099599;
}).length == this.updated.length;
}
})
The catch is that though a general native "query" operator can tell you that one array member meets the conditions, it cannot tell you that all of them do.
So the condition can either be tested with $map and $allElementsTrue, which are both available from MongoDB 2.6. With MongoDB 3.2 there is $filter and $size which are equivalent to the below JavaScript test.
Or alternately you use the JavaScript evaluation of $where to test the "filtered" array length against the original and see that they are still the same.
That's the additional logic to build in to see that all match the range conditions supplied. The aggregate method is native code as opposed to JavaScript interpretation. It runs much faster by comparison.
But you still want to keep that $elemMatch in all cases.
And of course, here are the matching documents:
{
"updated" : [
1461062102,
1461062316
],
"name" : "test1",
"etc" : "etc"
}
{
"updated" : [
1461084505
],
"name" : "test3",
"etc" : "etc"
}
{
"updated" : [
1461060430
],
"name" : "test4",
"etc" : "etc"
}