Inner array total count in mongodb - mongodb

I have a journal doc. Which contains details of journal_volumes, journal issues and journal articles. I have to list the journals along with the count of volumes, issues and articles in each journal.
Here is my doc:
{
"_id" : ObjectId("5c470fc3135edb4413b0ea24"),
"jnl_code" : "KEG",
"jnl_volumes" : [
{
"name" : "1",
"created_date" : "2019-03-01",
"status" : "0",
"issue_flag" : "0",
"jnl_issues" : [
{
"issue_name" : "1",
"created_date" : "2019-03-04",
"jnl_articles" : [
"test",
"test2"
]
},
{
"issue_name" : "2",
"created_date" : "2019-03-04",
"jnl_articles" : [
"a"
]
},
{
"issue_name" : "3",
"created_date" : "2019-03-04",
"jnl_articles" : [
"b"
]
},
{
"issue_name" : "3",
"created_date" : "2019-03-05",
"jnl_articles" : [
"Q"
]
}
]
},
{
"name" : "2",
"created_date" : "2019-03-01",
"status" : "0",
"issue_flag" : "0",
"jnl_issues" : [
{
"issue_name" : "1",
"created_date" : "2019-03-05",
"jnl_articles" : [
"W"
]
},
{
"issue_name" : "1",
"created_date" : "2019-03-05",
"jnl_articles" : [
"S"
]
},
{
"issue_name" : "1",
"created_date" : "2019-03-05",
"jnl_articles" : [
"R"
]
},
{
"issue_name" : "1",
"created_date" : "2019-03-05",
"jnl_articles" : [
"R"
]
}
]
},
{
"name" : "3",
"created_date" : "2019-03-05",
"status" : "0",
"issue_flag" : "0"
}
]
}
My requirement is to get the count of jnl_volumes, total jnl_issues count and total jnl_articles count in single query..
Thanks to Neil Lunn to redirect me to the similar question (Calculate the count of nested objects with C#
). I referred the answer and wrote a query:
db.getCollection('rvh_journals').aggregate([
{
$project: {
"volumes" : { "$size" : { "$ifNull" : [ "$jnl_volumes", [] ] } },
"issues" : {
"$sum" : {
"$map" : {
"input" : "$jnl_volumes",
"in": { "$size" : { "$ifNull" : [ "$$this.jnl_issues", [] ] } }
}
}
},
"articles" : {
"$sum" : {
"$map" : {
"input" : "$jnl_volumes.jnl_issues.jnl_articles",
"in" : { "$size" : { "$ifNull" : [ "$$this", [] ] } }
}
}
}
}
}
])
This returns with an incorrect article count. Actual article count is 9 but the query returns 8
{
"_id" : ObjectId("5c470fc3135edb4413b0ea24"),
"volumes" : 3,
"issues" : 8,
"articles" : 8
}

Yes I got curious after your edit to the previous question, and noticed your statement was incorrect.
This one is correct:
db.getCollection('rvh_journals').aggregate([
{ "$project": {
"volumes": { "$size": "$jnl_volumes" },
"issues": {
"$sum": {
"$map": {
"input": "$jnl_volumes",
"in": { "$size": { "$ifNull": ["$$this.jnl_issues", [] ] } }
}
}
},
"articles": {
"$sum": {
"$map": {
"input": "$jnl_volumes",
"in": {
"$sum": {
"$map": {
"input": { "$ifNull": [ "$$this.jnl_issues", [] ] },
"in": { "$size": { "$ifNull": [ "$$this.jnl_articles", [] ] } }
}
}
}
}
}
}
}}
])
Returns:
{
"_id" : ObjectId("5c470fc3135edb4413b0ea24"),
"volumes" : 3,
"issues" : 8,
"articles" : 9
}
Note the traversal of the arrays.
You might go and read some of the actual words I used on that original linked answer, because I would have explained that nesting arrays like this is not a good idea. More details on why it's not a good idea and practical approaches to take otherwise are on Updating a Nested Array with MongoDB

enter image description here
.itcount() - is showing distinct count.
Need to show total count of count variable which is declare in $group
Currently it is showing based on company.

Related

How update string to number field

/* 1 */
{
"_id" : ObjectId("62622dd73905f04f59db2971"),
"array1" : [
{
"_id" : "21",
"array2" : [
{
"_id" : "123",
"answeredBy" : [
"success"
]
},
{
"_id" : "124",
"answeredBy" : []
}
]
}
]
}
/* 2 */
{
"_id" : ObjectId("626230e03905f04f59db29f5"),
"array1" : [
{
"_id" : "22",
"array2" : [
{
"_id" : "223",
"answeredBy" : []
},
{
"_id" : "220",
"answeredBy" : []
}
]
}
]
}
How to convert
"_id" : "21", and "_id" : "22",
to
"_id" : 21, and "_id" : 22,
Here's one way to do it.
db.collection.update({
"array1._id": { "$exists": true }
},
[
{
"$set": {
"array1": {
"$map": {
"input": "$array1",
"as": "elem",
"in": {
"$mergeObjects": [
"$$elem",
{ "_id": { "$toInt": "$$elem._id" } }
]
}
}
}
}
}
],
{
"multi": true
})
Try it on mongoplayground.net.

How to find records whose some field value are all zero in mongo

I have lots of sensors, every sensor report a data every few seconds.
I need to find out the sensors whose data are all zero.
Furthurmore, I need to caculate the zero data ratio for every sensor.
Can any query can do this?
Any help will be highly appreciated.
The records are like
{
"_id" : ObjectId("61353065746e5e18a1d7c4ca"),
"sensor" : "SN54",
"category" : "w",
"data" : "7065",
"time" : ISODate("2021-09-06T05:02:29.308+08:00")
},
{
"_id" : ObjectId("61353065746e5e18a1d7c4c9"),
"sensor" : "SN68",
"category" : "w",
"data" : "0",
"time" : ISODate("2021-09-06T05:02:29.308+08:00")
},
Query (if data was in array (we dont need it here after the question update))
filter to keep the zero only, divides with all array size, and multiply with 100
if you want to get all zero, add a match where percentage=100
Test code here
db.collection.aggregate([
{
"$set": {
"percentage": {
"$multiply": [
{
"$cond": [
{
"$eq": [
"$data",
[]
]
},
0,
{
"$divide": [
{
"$size": {
"$filter": {
"input": "$data",
"as": "d",
"cond": {
"$eq": [
"$$d",
0
]
}
}
}
},
{
"$size": "$data"
}
]
}
]
},
100
]
}
}
}
])
Edit1 (for data that are not inside array)
Test code here
aggregate(
[ {
"$group" : {
"_id" : "$sensor",
"nzero" : {
"$sum" : {
"$cond" : [ {
"$eq" : [ "$data", "0" ]
}, 1, 0 ]
}
},
"count" : {
"$sum" : 1
}
}
}, {
"$set" : {
"sensor" : "$_id"
}
}, {
"$project" : {
"_id" : 0
}
}, {
"$project" : {
"sensor" : 1,
"percentage" : {
"$multiply" : [ {
"$divide" : [ "$nzero", "$count" ]
}, 100 ]
}
}
} ]
)

count documents by specific nested fields values with aggregation-framework in MongoDB

Here's my db.
{
"_id" : ObjectId("579cab6c6aba30f42a57a979"),
"iecode" : "P1111",
"country" : "India",
"totalTreatmentArms" : 3,
"treatmentArms" : [
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism1"
},
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism2"
},
{
"mechanismOrPkg" : "Package",
"mechanism" : "mechanism1"
}
]
}
{
"_id" : ObjectId("579cab7a6aba30f42a57a97a"),
"iecode" : "P1111",
"country" : "Canada",
"totalTreatmentArms" : 3,
"treatmentArms" : [
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism1"
},
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism2"
},
{
"mechanismOrPkg" : "Package",
"mechanism" : ""
}
]
}
{
"_id" : ObjectId("579cabac6aba30f42a57a97b"),
"iecode" : "P2222",
"country" : "India",
"totalTreatmentArms" : 1,
"treatmentArms" : [
{
"mechanismOrPkg" : "Package",
"mechanism" : ""
}
]
}
{
"_id" : ObjectId("579cabe76aba30f42a57a97c"),
"iecode" : "P3333",
"country" : "India",
"totalTreatmentArms" : 2,
"treatmentArms" : [
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism1"
},
{
"mechanismOrPkg" : "Package",
"mechanism" : ""
}
]
}
Please note that there are two records with iecode : P1111 and I want result distinguished on iecode so only one record will be considered(any one).
Now my requirement is I want count on field mechanismOrPkg. If it contains value Package then we will increment package with one. If the value is Mechanism then we will consider value of field mechanism and its respected values count will be considered.
So final result will be like this
{
"_id" : null,
"totalPackage" : 3,
"totalMechanism1" : 2,
"totalMechanism2" : 1
}
Please ask if it sounds confusing. Let me know even if this kind of aggregation is possible with query or I have to do server side filtering?
Thanks.
Edit
Possible values of mechanismOrPkg : Package or Mechanism
Possible values of mechanism : mechanism1 or mechanism2
Accomplishing the above would require using the $cond operator in the $sum accumulator operator. The $cond operator will evaluate a logical condition based on its first argument (if) and then returns the second argument where the evaluation is true (then) or the third argument where false (else). This converts the true/false logic into 1 and 0 numerical values that feed into $sum respectively. So for instance, when you want to aggregate the count for the "Package" value, the logic follows:
{
"$sum": {
"$cond": [ { "$eq": [ "$treatmentArms.mechanismOrPkg", "Package" ] }, 1, 0 ]
}
}
As a resulting pipeline, you need to run the aggregation operation
db.collection.aggregate([
{
"$group": {
"_id": "$iecode",
"treatmentArms": { "$first": "$treatmentArms" }
}
},
{ "$unwind": "$treatmentArms" },
{
"$group": {
"_id": null,
"totalPackage": {
"$sum": {
"$cond": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Package" ] },
1, 0
]
}
},
"totalMechanism1":{
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanism", "mechanism1" ] }
]
},
1,
0 ]
}
},
"totalMechanism2": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanism", "mechanism2" ] }
]
},
1,
0 ]
}
}
}
}
])
Sample Output
{
"_id" : null,
"totalPackage" : 3,
"totalMechanism1" : 2,
"totalMechanism2" : 1
}

mongodb aggregation match multiple $and on the same field

i have a document like this :
{
"ExtraFields" : [
{
"value" : "print",
"fieldID" : ObjectId("5535627631efa0843554b0ea")
},
{
"value" : "14",
"fieldID" : ObjectId("5535627631efa0843554b0eb")
},
{
"value" : "POLYE",
"fieldID" : ObjectId("5535627631efa0843554b0ec")
},
{
"value" : "30",
"fieldID" : ObjectId("5535627631efa0843554b0ed")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627631efa0843554b0ee")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627731efa0843554b0ef")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627831efa0843554b0f0")
},
{
"value" : "42",
"fieldID" : ObjectId("5535627831efa0843554b0f1")
},
{
"value" : "30",
"fieldID" : ObjectId("5535627831efa0843554b0f2")
},
{
"value" : "14",
"fieldID" : ObjectId("5535627831efa0843554b0f3")
},
{
"value" : "19",
"fieldID" : ObjectId("5535627831efa0843554b0f4")
}
],
"id" : ObjectId("55369e60733e4914550832d0"), "title" : "A product"
}
what i want is to match one or more sets from the ExtraFields array. For example, all the products that contain the values print and 30. Since a value may be found in more than one fieldID (like 0 or true) we need to create a set like
WHERE (fieldID : ObjectId("5535627631efa0843554b0ea"), value : "print")
Where i'm having problems is when querying more than one fields. The pipeline i came up with is :
db.products.aggregate([
{'$unwind': '$ExtraFields'},
{
'$match': {
'$and': [{
'$and': [{'ExtraFields.value': {'$in': ["A52A2A"]}}, {
'ExtraFields.fieldID': ObjectId("5535627631efa0843554b0ea")
}]
}
,
{
'$and': [{'ExtraFields.value': '14'}, {'ExtraFields.fieldID': ObjectId("5535627631efa0843554b0eb")}]
}
]
}
},
]);
This returns zero results, but this is what i want to do in theory. Match all items that contain set 1 AND all that contain set 2.
The end result should look like a faceted search output :
[
{
"_id" : {
"values" : "18",
"fieldID" : ObjectId("5535627831efa0843554b0f3")
},
"count" : 2
},
{
"_id" : {
"values" : "33",
"fieldID" : ObjectId("5535627831efa0843554b0f2")
},
"count" : 1
}
]
Any ideas?
You could try the following aggregation pipeline
db.products.aggregate([
{
"$match": {
"ExtraFields.value": { "$in": ["A52A2A", "14"] },
"ExtraFields.fieldID": {
"$in": [
ObjectId("5535627631efa0843554b0ea"),
ObjectId("5535627631efa0843554b0eb")
]
}
}
},
{
"$unwind": "$ExtraFields"
},
{
"$match": {
"ExtraFields.value": { "$in": ["A52A2A", "14"] },
"ExtraFields.fieldID": {
"$in": [
ObjectId("5535627631efa0843554b0ea"),
ObjectId("5535627631efa0843554b0eb")
]
}
}
},
{
"$group": {
"_id": {
"value": "$ExtraFields.value",
"fieldID": "$ExtraFields.fieldID"
},
"count": {
"$sum": 1
}
}
}
])
With the sample document provided, this gives the output:
/* 1 */
{
"result" : [
{
"_id" : {
"value" : "14",
"fieldID" : ObjectId("5535627631efa0843554b0eb")
},
"count" : 1
}
],
"ok" : 1
}

How do I create nested aggregations with count on MongoDB?

I am learning MongoDB in order to see if it matches our needs.
Currently we use heavily aggregations, so I am testing the flexibility of the Aggregation Framework.
I started with this hierarchy
db.companytest3.insert({"name":"A", age:7})
db.companytest3.insert({"name":"B", age:17, owner:"A"})
db.companytest3.insert({"name":"C", age:12, owner:"A"})
db.companytest3.insert({"name":"D", age:7, owner:"B"})
db.companytest3.insert({"name":"E", age:13, owner:"B"})
db.companytest3.insert({"name":"F", age:23, owner:"C"})
So I have:
db.companytest3.find()
{ "_id" : ObjectId("5457c2c0fa82c305e0b80006"), "name" : "A", "age" : 7 }
{ "_id" : ObjectId("5457c2cafa82c305e0b80007"), "name" : "A", "age" : 7 }
{ "_id" : ObjectId("5457c2d0fa82c305e0b80008"), "name" : "B", "age" : 17, "owner" : "A" }
{ "_id" : ObjectId("5457c2d6fa82c305e0b80009"), "name" : "C", "age" : 12, "owner" : "A" }
{ "_id" : ObjectId("5457c2ddfa82c305e0b8000a"), "name" : "D", "age" : 7, "owner" : "B" }
{ "_id" : ObjectId("5457c2e4fa82c305e0b8000b"), "name" : "E", "age" : 13, "owner" : "B" }
{ "_id" : ObjectId("5457c2eafa82c305e0b8000c"), "name" : "F", "age" : 23, "owner" : "C" }
My goal is to aggregate the children using their ages, so I have something like this:
{
"_id" : null,
"children" : [
{
"range:" : "lower than 10",
total: 1,
names: ["A"]
}
{
"range:" : "higher than 10",
total: 0,
names: []
}
],
"total" : 1
}
{
"_id" : "A",
"children" : [
{
"range:" : "lower than 10",
total: 0,
names: []
}
{
"range:" : "higher than 10",
total: 2,
names: ["C","B"]
}
],
"total" : 1
}
{
"_id" : "B",
"children" : [
{
"range:" : "lower than 10",
total: 1,
names: ["D"]
}
{
"range:" : "higher than 10",
total: 13,
names: ["E"]
}
],
"total" : 1
}
{
"_id" : "C",
"children" : [
{
"range:" : "lower than 10",
total: 0,
names: []
}
{
"range:" : "higher than 10",
total: 1,
names: ["F"]
}
],
"total" : 1
}
I feel I am getting near, I've got this query:
db.companytest3.aggregate(
{ $project: {
"_id": 0,
"range": {
$concat: [{
$cond: [ { $lte: ["$age", 10] }, "até 10", "" ]
}, {
$cond: [ { $gte: ["$age", 11] }, "mais de 10", "" ]
}]
},
"owner": "$owner",
"name" : "$name"
}
},
{
$group: {
_id: { owner: "$owner", range: "$range" },
children: { $addToSet: { name: "$name", range: "$range"} } ,
total: { $sum: 1}
}
},
{
$group: {
_id: { owner:"$_id.owner" },
children: { $addToSet: "$children" }
}
}
)
which gives me the following output:
{ "_id" : { "owner" : null }, "children" : [ [ { "name" : "A", "range" : "até 10" } ] ] }
{ "_id" : { "owner" : "A" }, "children" : [ [ { "name" : "C", "range" : "mais de 10" }, { "name" : "B", "range" : "mais de 10" } ] ] }
{ "_id" : { "owner" : "B" }, "children" : [ [ { "name" : "D", "range" : "até 10" } ], [ { "name" : "E", "range" : "mais de 10" } ] ] }
{ "_id" : { "owner" : "C" }, "children" : [ [ { "name" : "F", "range" : "mais de 10" } ] ] }
Now I am having issues to group the items by owner and keep sum the total, I am stuck and I do not know how to proceed. I've been trying many diferent alternatives using groups variations but I do not feel they are worth posting here.
How can I change my current query so I group the children by range and add the count?
thanks! :D
It should be possible in earlier versions, but even basically looking at how you want to manipulate the result, the simplest way I can see is with the help of some operators introduced in MongoDB 2.6.
db.companytest3.aggregate([
{ "$group": {
"_id": "$owner",
"lowerThanTenNames": {
"$addToSet": {
"$cond": [
{ "$lte": [ "$age", 10 ] },
"$name",
false
]
}
},
"lowerThanTenTotal": {
"$sum": {
"$cond": [
{ "$lte": [ "$age", 10 ] },
1,
0
]
}
},
"moreThanTenNames": {
"$addToSet": {
"$cond": [
{ "$gte": [ "$age", 11 ] },
"$name",
false
]
}
},
"moreThanTenTotal": {
"$sum": {
"$cond": [
{ "$gte": [ "$age", 11 ] },
1,
0
]
}
}
}},
{ "$project": {
"children": {
"$map": {
"input": { "$literal": ["L", "M"] },
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el", "L" ] },
{
"range": { "$literal": "lower than 10" },
"total": "$lowerThanTenTotal",
"names": {
"$setDifference": [
"$lowerThanTenNames",
[false]
]
}
},
{
"range": { "$literal": "higher than 10" },
"total": "$moreThanTenTotal",
"names": {
"$setDifference": [
"$moreThanTenNames",
[false]
]
}
}
]
}
}
},
"total": { "$add": [ "$lowerThanTenTotal", "$moreThanTenTotal" ]},
}},
{ "$sort": { "_id": 1 } }
])
Basically you want to separate these out into two sets of results for each grouping, being one for each age range. Due to the use of conditional operators, the "names" sets then need to be filtered for any false values where the conditions did not match.
The other thing that needs to be done is to coerce these results from separate fields into an array. The $map operator makes this simple by just providing a two element template with effectively "A/B" choices to do the re-mapping.
Since we had discrete fields here before they were re-mapped onto an array, you can just supply each "total" field as an argument to $add in order to get the combined total.
Produces exactly this:
{
"_id" : null,
"children" : [
{
"range" : "lower than 10",
"total" : 1,
"names" : ["A"]
},
{
"range" : "higher than 10",
"total" : 0,
"names" : [ ]
}
],
"total" : 1
}
{
"_id" : "A",
"children" : [
{
"range" : "lower than 10",
"total" : 0,
"names" : [ ]
},
{
"range" : "higher than 10",
"total" : 2,
"names" : ["C","B"]
}
],
"total" : 2
}
{
"_id" : "B",
"children" : [
{
"range" : "lower than 10",
"total" : 1,
"names" : ["D"]
},
{
"range" : "higher than 10",
"total" : 1,
"names" : ["E"]
}
],
"total" : 2
}
{
"_id" : "C",
"children" : [
{
"range" : "lower than 10",
"total" : 0,
"names" : [ ]
},
{
"range" : "higher than 10",
"total" : 1,
"names" : ["F"]
}
],
"total" : 1
}