Find a single field from a nested Array in MongoDB - mongodb

Here I have a sample Nested Array. I have a problem with writing proper queries on this collection which is deeply nested.
{
"productUUID" : "craft001",
"providers": [
{
"providerUUID": "prov001",
"orgs": [
{
"orgUUID": "org001",
"location": {
"buildings": [
{
"buildingUUID": "sit001",
"floors": [
{
"floorUUID": "GrndFlr",
"assets": [ ],
"agents": [ ],
"users": [ ]
},
{
"floorUUID": "1stFlr",
"assets": [ ],
"agents": [ ],
"users": [ ]
}
]
},
{
"buildingUUID": "ist001",
"floors": [ ]
}
]
}
},
{
"orgUUID": "org002",
"location": {
"buildings": [ ]
}
}
]
},
{
"providerUUID": "prov002",
"orgs": [ ]
}
]
}
Question in simple words, "1. Get all orgUUIDs which fall under providerUUID: "prov001"".
Similarly, "2. Get all floorUUIDs where "buildingUUID": "sit001"".
If someone can help me with the 1st ques, I hope I can solve the 2nd ques myself.

Mongo aggregation use to finding to nested documents. First unwind all providers array then use match to match providerUUID as given prov001 then used project to get all orgUUID and aggregation query as :
db.collectionName.aggregate({"$unwind":"$providers"},
{"$match":{"providers.providerUUID":"prov001"}},
{"$project":{"orgUUID":"$providers.orgs.orgUUID"}},
{"$unwind":"$orgUUID"},
{"$project":{"_id":0,"orgUUID":1}}
).pretty()
this will returns all orgUUID in an array.
If you use $elemMacth then this operator having it's own limitation as
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
elemMatch query as :
db.collectionName.find({"providers":{"$elemMatch":{"providerUUID":"prov001"}}},
{"providers.$.providerUUID.orgs.orgUUID":1}).pretty()
it returns whole matching providers array.
I hope you will find out "2" question query yourself, If you having any trouble with finding with "2" query I will post "2" query also. Try to yourself to find out second query answer yourself :)

For some reason, I had to change the data in collection as following.
{
"productUUID": "prod001",
"providers": [
{
"providerUUID": "prov001",
"orgs": [
{
"orgUUID": "org001",
"floors": [
{ "floorUUID": "SIT_GrndFlr" },
{ "floorUUID": "SIT_1stFlr" }
],
"assets": [{},{}],
"agents": [{},{}],
"users": [{},{}]
},
{
"orgUUID": "org002",
"floors": [
{ "floorUUID": "IST_1stFlr" },
{ "floorUUID": "IST_2ndFlr" }
],
"assets": [{},{}],
"agents": [{},{}],
"users": [{},{}]
}
]
},
{
"providerUUID": "prov002",
"orgs": [
{
"orgUUID": "org001",
"floors": [{},{}],
"assets": [{},{}],
"agents": [{},{}],
"users": [{},{}]
},
{
"orgUUID": "org002",
"floors": [{},{}],
"assets": [{},{}],
"agents": [{},{}],
"users": [{},{}]
}
]
}
]
}
so, now with the help of #yogesh, I was introduced to aggregate and was able to write queries for my questions.
1. Get all `orgUUID`s under `providerUUID: "prov001"`.
db.collectionName.aggregate({"$unwind":"$providers"},
{"$match":{"providers.providerUUID":"prov001"}},
{"$project":{"orgUUID":"$providers.orgs.orgUUID"}},
{"$unwind":"$orgUUID"},
{"$project":{"_id":0,"orgUUID":1}}
)
2. Get all `floorUUID`s under `orgUUID : "org001"`.
db.collectionName.aggregate(
{ "$unwind" : "$providers" },
{ "$match" : { "providers.providerUUID" : "prov001" } },
{ "$unwind" : "$providers.orgs" },
{ "$match" : { "providers.orgs.orgUUID" : "org001" } },
{ "$project" : { "floorUUID" : "$providers.orgs.floors.floorUUID" } },
{ "$unwind" : "$floorUUID" },
{ "$project" : { "_id":0 , "floorUUID" : 1 } }
)

Related

mongodb update document from first element of array

Consider a collection client with the following documents:
[
{
"id": 1,
"Name": "Susie",
"ownership" : {
"ownershipContextCode" : "C1"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_1",
"clientId": "11"
}
]
},
{
"id": 2,
"Name": "John",
"ownership" : {
"ownershipContextCode" : "C2"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_2",
"clientId": "22"
}
]
}
]
I am attempting to set a field (ownershipClientCode) as the first element of the clientIds array.
The result should be like that:
[
{
"id": 1,
"Name": "Susie",
"ownership" : {
"ownershipContextCode" : "C1",
"ownershipClientCode" : "clientClusterCode_1"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_1",
"clientId": "11"
}
],
},
{
"id": 2,
"Name": "John",
"ownership" : {
"ownershipContextCode" : "C2",
"ownershipClientCode" : "clientClusterCode_2"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_2",
"clientId": "22"
}
],
}
]
I'm using this query but I can't get sub object from the first element in the array
db.collection.aggregate([
{
$addFields: {
"Last Semester": {
"$arrayElemAt": [
"$clientIds",
0
]
}
}
}
])
This query add the all object but I want only the field (clientClusterCode).
Some thing like that
db.collection.aggregate([
{
$addFields: {
"Last Semester": {
"$arrayElemAt": [
"$clientIds",
0
].clientClusterCode
}
}
}
])
I'm using mongodb 4.0.0
You're very close: https://mongoplayground.net/p/HY1Pj0P4z12
db.collection.aggregate([
{
$addFields: {
"ownership.ownershipClientCode": {
"$arrayElemAt": [
"$clientIds.clientClusterCode",
0
]
}
}
}
])
You can use the dot notation within the $arrayElemAt as well as when you defining the field name.
To directly set the field, do something like this (use aggregation in the update): https://mongoplayground.net/p/js-usEJSH_A
db.collection.update({},
[
{
$set: {
"ownership.ownershipClientCode": {
"$arrayElemAt": [
"$clientIds.clientClusterCode",
0
]
}
}
}
],
{
multi: true
})
Note: The second method to update needs to be an array, so that it functions as an pipeline.

Mongo Query for Multiple conditions with limit and skip

The document format of all collections in db is as :
{
"_id": {
"$oid": "5e0983863bcf0dab51f2872b"
},
"word": "never", // get the `word` value for each of below queries
"wordset_id": "a42b50e85e",
"meanings": [{
"id": "1f1bca9d9f",
"def": "not ever",
"speech_part": "adverb",
"synonyms": ["ne'er"]
}, {
"id": "d35f973ed0",
"def": "not at all",
"speech_part": "adverb"
}]
}
I am trying to query for word/words,
1) where word length is 4 and speech_part is noun containing ac (%something% in sql) in it (The result would jack,......)
2) how to add all three starting with , ending with , containing in single query (eg: starting with j , containing ac , ending with k----> would give jack)
I have tried for 1) as:
pipeline = [
{
"$match": {
"meanings.speech_part": "noun",
"word": "/ac/",
"$expr": {"$eq": [{"$strLenCP": "$word"}, 4]}
}
}
]
query=db[collection].aggregate(pipeline)
But I got no result for this, also how to add skip and limit for an aggregate , should i use facet ?
referring SO answer, i found this:
db.Order.aggregate([
{ '$match' : { "company_id" : ObjectId("54c0...") } },
{ '$sort' : { 'order_number' : -1 } },
{ '$facet' : {
metadata: [ { $count: "total" }, { $addFields: { page: NumberInt(3) } } ],
data: [ { $skip: 20 }, { $limit: 10 } ] // As shown here------
} }
] )
For reference Pythonic pipeline would look like this:
pipeline = [
{
'$match': {
"$expr": {"$eq": [{"$strLenCP": "$word"}, 4]},
'word': re.compile('ac'),
'meanings.speech_part': "noun"
}
}
]

MongoDB - Performing an upsert on an array if arrayFilters are not satisfied

I have the following document stored in mongo:
{
"_id" : ObjectId("5d1a08d2329a3c1374f176df"),
"associateID" : "1234567",
"associatePreferences" : [
{
"type" : "NOTIFICATION",
"serviceCode" : "service-code",
"eventCode" : "test-template",
"preferences" : [
"TEXT",
"EMAIL"
]
},
{
"type" : "URGENT_NOTIFICATION",
"serviceCode" : "service-code",
"eventCode" : "test-template",
"preferences" : [
"TEXT"
]
}
]
}
I am basically trying to query one of the elements of the associatePreferences array based off of its type, serviceCode, and eventCode and add a new value to the preferences array. However, if that combination of type, serviceCode, and eventCode is not present, I would like to add a new element to the associatePreferences array with those values. This is my current query:
db.user_communication_preferences.update(
{'associateID':'testassociate'},
{$addToSet:{'associatePreferences.$[element].preferences':"NEW_VALUE"}},
{arrayFilters:[{'element.serviceCode':'service-code-not-present', 'element.eventCode':'event-code-not-present','element.type':'URGENT_NOTIFICATION'}]}
)
This query works if all of the arrayFilters are present in the an element of associatePreferences, but it does not add a new element if it is not present. What am I missing?
You can use aggregation pipeline to check the existence of the element, then append the element to associatePreferences array conditionally. Finally, using the aggregation result to update back your document.
db.user_communication_preferences.aggregate([
{
"$match": {
"associateID": "testassociate"
}
},
{
"$addFields": {
"filteredArray": {
"$filter": {
"input": "$associatePreferences",
"as": "pref",
"cond": {
$and: [
{
$eq: [
"$$pref.type",
"URGENT_NOTIFICATION"
]
},
{
$eq: [
"$$pref.eventCode",
"event-code-not-exists"
]
},
{
$eq: [
"$$pref.serviceCode",
"service-code-not-exists"
]
}
]
}
}
}
}
},
{
$addFields: {
"needAddElement": {
$eq: [
{
"$size": "$filteredArray"
},
0
]
}
}
},
{
"$addFields": {
"associatePreferences": {
"$concatArrays": [
"$associatePreferences",
{
"$cond": {
"if": {
$eq: [
"$needAddElement",
true
]
},
"then": [
{
"type": "URGENT_NOTIFICATION",
"serviceCode": "service-code-not-exists",
"eventCode": "event-code-not-exists",
"preferences": [
"TEXT"
]
}
],
"else": []
}
}
]
}
}
}
]).forEach(result){
db.user_communication_preferences.update({
_id : result._id
}, {
$set: {
"associatePreferences" : result.associatePreferences
}
})
}

Dynamically Querying From an Input Object

I'm trying to dynamically query a database that looks like this:
db.test.insert({
"_id" : ObjectId("58e574a768afb6085ec3a388"),
"place": "A",
"tests" : [
{
"name" : "1",
"thing" : "X",
"evaluation" : [
{
"_id": ObjectId("58f782fbbebac50d5b2ae558"),
"aHigh" : [1,2],
"aLow" : [ ],
"zHigh" : [ ],
"zLow" : [1,3]
},
{
"_id": ObjectId("58f78525bebac50d5b2ae5c9"),
"aHigh" : [1,4],
"aLow" : [2],
"zHigh" : [ 3],
"zLow" : [ ]
},
{
"_id": ObjectId("58f78695bebac50d5b2ae60e"),
"aHigh" : [ ],
"aLow" : [1,2,3],
"zHigh" : [1,2,3,4],
"zLow" : [ ]
},]
},
{
"name" : "1",
"thing" : "Y",
"evaluation" : [
{
"_id": ObjectId("58f78c37bebac50d5b2ae704"),
"aHigh" : [1,3],
"aLow" : [4],
"zHigh" : [ ],
"zLow" : [3]
},
{
"_id": ObjectId("58f79159bebac50d5b2ae75c"),
"aHigh" : [1,3,4],
"aLow" : [2],
"zHigh" : [2],
"zLow" : [ ]
},
{
"_id": ObjectId("58f79487bebac50d5b2ae7f1"),
"aHigh" : [1,2,3],
"aLow" : [ ],
"zHigh" : [ ],
"zLow" : [1,2,3,4]
},]
}
]
})
db.test.insert({
"_id" : ObjectId("58eba09e51f7f631dd24aa1c"),
"place": "B",
"tests" : [
{
"name" : "2",
"thing" : "Y",
"evaluation" : [
{
"_id": ObjectId("58f7879abebac50d5b2ae64f"),
"aHigh" : [2],
"aLow" : [3 ],
"zHigh" : [ ],
"zLow" : [1,2,3,4]
},
{
"_id": ObjectId("58f78ae1bebac50d5b2ae6db"),
"aHigh" : [ ],
"aLow" : [ ],
"zHigh" : [ ],
"zLow" : [3,4]
},
{
"_id": ObjectId("58f78ae1bebac50d5b2ae6dc"),
"aHigh" : [1,2],
"aLow" : [3,4],
"zHigh" : [ ],
"zLow" : [1,2,3,4]
},]
}
]
})
In order to query the database, I have an object that is created by another part of my program. It comes in the form of:
var outputObject = {
"top": {
"place": [
"A"
]
},
"testing": {
"tests": {
"name": [
"1",
],
"thing": [
"X",
"Y"
]
}
}
}
I then use that outputObject and $match statements within the aggregate framework to execute the query. I have included two queries which do not seem to work.
db.test.aggregate([
{$match: {outputObject.top}},
{$unwind: '$tests'},
{$match: {outputObject.testing}},
{$unwind: '$tests.evaluation'},
{$group: {_id: null, uniqueValues: {$addToSet: "$tests.evaluation._id"}}}
])
db.test.aggregate([
{$match: {$and: [outputObject.top]}},
{$unwind: '$tests'},
{$match: {$and: [outputObject.testing]}},
{$unwind: '$tests.evaluation'},
{$group: {_id: null, uniqueValues: {$addToSet: "$tests.evaluation._id"}}}
])
However, this approach does not seem to be functioning. I have a couple questions:
Do I need to modify the object outputObject before applying it to the $match statement?
Are my queries correct?
Should I be using $and or $in in combination with the $match statement?
What code will produce the desired result?
Currently using mongoDB 3.4.4
You have a couple of problems here. Firstly the array arguments in your input value should rather be compared with $in which many "any of these in the list" in order to match.
The second problem is that that since the paths are "nested" here you actually need to transform to "dot notation" otherwise you have another variant of the first problem where the conditions would be looking in the "test" array for elements that only have the supplied fields you specify in the input.
So unless you "dot notate" the path as well then since your array items also contain "evaluation" which is not supplied in the input, then it would not match as well.
The other issue here, but easily corrected is the "top" and "testing" separation here is not actually needed. Both conditions actually apply within "both" the $match stages in your pipeline. So you could in fact "flatten" that, as the example shows:
var outputObject = {
"top" : {
"place" : [
"A"
]
},
"testing" : {
"tests" : {
"name" : [
"1"
],
"thing" : [
"X",
"Y"
]
}
}
};
function dotNotate(obj,target,prefix) {
target = target || {},
prefix = prefix || "";
Object.keys(obj).forEach(function(key) {
if ( Array.isArray( obj[key] ) ) {
return target[prefix + key] = { "$in": obj[key] };
} else if ( typeof(obj[key]) === "object" ) {
dotNotate(obj[key],target,prefix + key + ".");
} else {
return target[prefix + key] = obj[key];
}
});
return target;
}
// Run the transformation
var queryObject = dotNotate(Object.assign(outputObject.top,outputObject.testing));
This produces queryObject which now looks like:
{
"place" : {
"$in" : [
"A"
]
},
"tests.name" : {
"$in" : [
"1"
]
},
"tests.thing" : {
"$in" : [
"X",
"Y"
]
}
}
And then you can run the aggregation:
db.test.aggregate([
{ '$match': queryObject },
{ '$unwind': "$tests" },
{ '$match': queryObject },
{ '$unwind': "$tests.evaluation" },
{ '$group': {
'_id': null,
'uniqueValues': {
'$addToSet': "$tests.evaluation._id"
}
}}
])
Which correctly filters the objects
{
"_id" : null,
"uniqueValues" : [
ObjectId("58f79487bebac50d5b2ae7f1"),
ObjectId("58f79159bebac50d5b2ae75c"),
ObjectId("58f782fbbebac50d5b2ae558"),
ObjectId("58f78c37bebac50d5b2ae704"),
ObjectId("58f78525bebac50d5b2ae5c9"),
ObjectId("58f78695bebac50d5b2ae60e")
]
}
Please note that the conditions you supply here actually matches all documents and array entries you supplied in your question anyway. But it will of course actually remove anything that does not match.
Also ideally the "initial" query would rather use $elemMatch
{
"place" : {
"$in" : [
"A"
]
},
"tests": {
"$elemMatch": {
"name" : { "$in" : [ "1" ] },
"thing" : { "$in" : [ "X", "Y" ] }
}
}
}
Which would actually filter all of the documents properly in the initial query stage, since it would only select documents that actually had array elements which did in fact match "only" those conditions as opposed to the dot notated form in the "initial" query which would also return documents where the notated conditions for the "test" array were met in "any element" instead of "both conditions" on the element. But that may be another exercise to consider as the restructured query can apply to both the initial and "inner" filters without the $elemMatch.
Actually with thanks to this nice solution to a "Deep Object Merge" without additional library dependencies, you can use the $elemMatch like this:
var outputObject = {
"top" : {
"place" : [
"A"
]
},
"testing" : {
"tests" : {
"name" : [
"1"
],
"thing" : [
"X",
"Y"
]
}
}
};
function dotNotate(obj,target,prefix) {
target = target || {},
prefix = prefix || "";
Object.keys(obj).forEach(function(key) {
if ( Array.isArray( obj[key] ) ) {
return target[prefix + key] = { "$in": obj[key] };
} else if ( typeof(obj[key]) === "object" ) {
dotNotate(obj[key],target,prefix + key + ".");
} else {
return target[prefix + key] = obj[key];
}
});
return target;
}
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (var key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
var queryObject = dotNotate(Object.assign(outputObject.top,outputObject.testing));
// Replace dot with $elemMatch
var initialQuery = Object.keys(queryObject).map( k => (
( k.split(/\./).length > 1 )
? { [k.split(/\./)[0]]: { "$elemMatch": { [k.split(/\./)[1]]: queryObject[k] } } }
: { [k]: queryObject[k] }
)).reduce((acc,curr) => mergeDeep(acc,curr),{})
db.test.aggregate([
{ '$match': initialQuery },
{ '$unwind': "$tests" },
{ '$match': queryObject },
{ '$unwind': "$tests.evaluation" },
{ '$group': {
'_id': null,
'uniqueValues': {
'$addToSet': "$tests.evaluation._id"
}
}}
])
With the pipeline being sent to the server as:
[
{
"$match" : {
"place" : {
"$in" : [
"A"
]
},
"tests" : {
"$elemMatch" : {
"name" : {
"$in" : [
"1"
]
},
"thing" : {
"$in" : [
"X",
"Y"
]
}
}
}
}
},
{
"$unwind" : "$tests"
},
{
"$match" : {
"place" : {
"$in" : [
"A"
]
},
"tests.name" : {
"$in" : [
"1"
]
},
"tests.thing" : {
"$in" : [
"X",
"Y"
]
}
}
},
{
"$unwind" : "$tests.evaluation"
},
{
"$group" : {
"_id" : null,
"uniqueValues" : {
"$addToSet" : "$tests.evaluation._id"
}
}
}
]
Also your $group is probably better written as:
{ "$group": { "_id": "$tests.evaluation._id" } }
Which returns "distinct" just like $addToSet does, but also puts the output into separate documents, instead of trying to combine into "one" which is probably not the best practice and could in extreme cases break the BSON limit of 16MB. So it is generally better to obtain "distinct" in that way instead.
It is better to agree on a fixed format for outputObject and write aggregation query accordingly.
You can now process the outputObject to inject the query operators and transform the keys to match the fields.
Something like below.
{
"top": {
"place": {
"$in": [
"A"
]
}
},
"testing": {
"tests.name": {
"$in": [
"1"
]
},
"tests.thing": {
"$in": [
"X",
"Y"
]
}
}
}
JS Code
var top = outputObject.top;
Object.keys(top).forEach(function(a) {
top[a] = {
"$in": top[a]
};
});
var testing = outputObject.testing;
Object.keys(testing).forEach(function(a) {
Object.keys(testing[a]).forEach(function(b) {
var c = [a + "." + b];
testing[c] = {
"$in": testing[a][b]
};
})
delete testing[a];
});
You can now use your aggregation query
db.test.aggregate([{
$match: top
},
{
$unwind: "$tests"
},
{
$match: testing
},
{
$unwind: "$tests.evaluation"
},
{
$group: {
_id: null,
uniqueValues: {
$addToSet: "$tests.evaluation._id"
}
}
}
])
You can refactor your code to use below aggregation pipeline in 3.4
Process your output object ( includes $in operator ) to
{
"top": {
"place": {
"$in": [
"A"
]
}
},
"testing": {
"tests": {
"name": [
"1"
],
"thing": [
"X",
"Y"
]
}
}
};
JS Code
var top = outputObject.top;
Object.keys(top).forEach(function(a) {top[a] = {"$in":top[a]};});
Aggregation:
[
{
"$match": top
},
{
"$addFields": {
"tests": {
"$filter": {
"input": "$$tests",
"as": "res",
"cond": {
"$and": [
{
"$in": [
"$$res.name",
outputObject.testing.tests.name
]
},
{
"$in": [
"$$res.thing",
outputObject.testing.tests.thing
]
}
]
}
}
}
}
},
{
"$unwind": "$tests.evaluation"
},
{
"$group": {
"_id": null,
"uniqueValues": {
"$addToSet": "$tests.evaluation._id"
}
}
}
]

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