Count array documents in mongodb collection - mongodb

i have a entity collection like this:
{
"_id" : ObjectId("55f93cedc4fd0e1f309aea64"),
"entityType" : "1",
"identifierIdentity" : [
{
"identifierTypeCode" : "NPI",
"identifierValue" : "111"
},
{
"identifierTypeCode" : "NPI",
"identifierValue" : "123"
},
{
"identifierTypeCode" : "NPI",
"identifierValue" : "141"
},
{
"identifierTypeCode" : "SSN",
"identifierValue" : "155"
}
]
}
{
"_id" : ObjectId("55f93cedc4fd0e1f309aea65"),
"entityType" : "2",
"identifierIdentity" : [
{
"identifierTypeCode" : "NPI",
"identifierValue" : "111"
},
{
"identifierTypeCode" : "NPI",
"identifierValue" : "123"
},
{
"identifierTypeCode" : "SSN",
"identifierValue" : "155"
}
]
}
{
"_id" : ObjectId("55f93cedc4fd0e1f309aea66"),
"entityType" : "3",
"identifierIdentity" : [
{
"identifierTypeCode" : "SSN",
"identifierValue" : "111"
},
{
"identifierTypeCode" : "SSN",
"identifierValue" : "123"
}
]
}
in the above identifierIdentity is an array of documents.
I am trying to get the entity count where "identifierTypeCode" : "NPI" is greater than or equal to two.
I am able to get this using java code but i have millions of records which is taking a lot of time. I want to know whether is their a way i can achieve this in a single query.

The $redact aggregate operator will make short work of this, coupled with a match for arrays that have at least two elements to trim thngs down:
db.collection.aggregate([
{ "$match": {
"identifierIdentity.identifierTypeCode": "NPI",
"identifierIdentity.1": { "$exists": true }
}},
{ "$redact": {
"$cond": {
"if": {
"$gte": [
{ "$size": { "$setDifference": [
{ "$map": {
"input": "$identifierIdentity",
"as": "el",
"in": {
"$cond": {
"if": { "$eq": ["$$el.identifierTypeCode", "NPI"] },
"then": "$$el",
"else": false
}
}
}},
[false]
] } },
2
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Basically match first to cut down the possible documents to procces, the the $redact does another logical match by filtering out the matches from the array and counting the size of the result. Where greater or equal to 2, then you keep the result, or otherwise discard it.

Related

Concat int and string array fields which are in different arrays

{
"no" : "2020921008981",
"date" : ISODate("2020-04-01T05:19:02.263+0000"),
"sale" : {
"soldItems" : [
{
"itemId" : "5b55ac7f0550de00210a3b24",
"qty" : NumberInt(1),
},
{
"itemId" : "5b55ac7f0550de00210a3b25",
"qty" : NumberInt(2),
}
],
"items" : [
{
"_id" : ObjectId("5b55ac7f0550de00210a3b24"),
unit :"KG"
},
{
"_id" : ObjectId("5b55ac7f0550de00210a3b25"),
unit :"ML"
}
]
}
}
Desired output :
{
"no" : "2020921008981",
"sale" : {}
"qtyList" : "1 KG \n 2 ML"
}
In order to build itemQtyList output field, two fields from different arrays (string and int) should be used. Couldn't find any reference for doing that. Any idea would be appreciated.
You can use below aggregation
db.collection.aggregate([
{ "$project": {
"itemQtyList": {
"$reduce": {
"input": { "$range": [0, { "$size": "$sale.soldItems" }] },
"initialValue": "",
"in": {
"$concat": [
"$$value",
{ "$cond": [{ "$eq": ["$$this", 0] }, "", " \n "] },
{ "$toString": {
"$arrayElemAt": [
"$sale.soldItems.qty",
"$$this"
]
}},
" ",
{ "$arrayElemAt": ["$sale.items.unit", "$$this"] }
]
}
}
}
}}
])
MongoPlayground

push on condition into array

We need to push/addtoset only if the key in the document ne []
How do we achieve this
{
"_id" : ObjectId("xxxxxx"),
"shop" : "REQ4",
"bolt" : "5647",
"nut" : "1111",
}
{
"_id" : ObjectId("xxxxxx"),
"shop" : "REQ4",
"bolt" : "2314",
"nut":[]
}
Aggregates.group("$shop", Accumulators.addToSet("bolt", "$bolt"),Accumulators.addToSet("nut", "nut"))//only if nut ne []
Expected output:
{ "_id" : "REQ4", "bolt" : ["5647", "2314"], "nut" : ["1111"]
You can first $push then can use $filter to ruled out []
db.collection.aggregate([
{ "$group": {
"_id": "$shop",
"bolt": { "$push": "$bolt" },
"nut": { "$push": "$nut" }
}},
{ "$addFields": {
"nut": {
"$filter": {
"input": "$nut",
"cond": { "$ne": ["$$this", []] }
}
}
}}
])

How to add two collections with single aggregation

I am new to MongoDb and would appreciate some help with this query. I wrote the following aggregation pipeline. I wrote the query from collection1 I got the output ("Conventional Energy" : 0.0036) and I wrote the query collection2 I got the output (LastMonthConsumption" : 2.08) but how to add two collection with single aggregation with(LastMonthConsumption" : 2.08 * Conventional Energy" : 0.0036/Conventional Energy" : 0.0036) this is my required output
I have this data in mongodb:
COLLECTION 1:DATA
{
"slcId" : "51",
"clientId" : "1",
"dcuId" : "1",
"type" : "L",
"officeId" : "200-24",
"lampStatus" : "OFF",
"cummulativeKWH" : 133.7,
"powerFactor" : 1.0,
"createDate" : ISODate("2018-09-06T00:01:34.816Z")
},
{
"slcId" : "52",
"clientId" : "1",
"dcuId" : "1",
"type" : "L",
"officeId" : "200-24",
"lampStatus" : "OFF",
"cummulativeKWH" : 133.7,
"powerFactor" : 1.0,
"createDate" : ISODate("2018-09-07T21:01:34.816Z")
}
COLLECTION2:DATA
{
"_class" : "MongoStreetLightMonthlyVo",
"timeId" : ISODate("2018-08-04T16:40:08.817Z"),
"vendor" : "CIMCON",
"slcId" : "123450",
"mongoStreetLightChildVo" : {
"totalConsumptionMtd" : 2.08,
"prevConsumptionMtd" : 3.45,
"perChargeKWH" : 9.85,
}
},
{
"_class" : "MongoStreetLightMonthlyVo",
"timeId" : ISODate("2018-09-04T16:40:08.817Z"),
"vendor" : "CIMCON",
"slcId" : "123450",
"mongoStreetLightChildVo" : {
"totalConsumptionMtd" : 2.08,
"prevConsumptionMtd" : 3.45,
"perChargeKWH" : 9.85,
}
}
Collection1:
db.collection1.aggregate([
{ $match:{"type" : "L"}},
{
$count: "TOTAL_Lights"
},
{ "$project": {
"Conventional Energy": {
"$divide": [
{ "$multiply": [
{ "$multiply": [ "$TOTAL_Lights" ,0.12 ] },
]},
1000
]
}
}},
])
output: {"Conventional Energy" : 0.0036}
Collection2:
db.collection2.aggregate(
[
// Stage 1
{
$group: {
_id:{year:{$year:"$timeId"},month:{$month:"$timeId"} },
LastMonthConsumption : {$sum:"$mongoStreetLightChildVo.totalConsumptionMtd"},
}
},
{
$redact: {
$cond: { if: { $and:[
{$eq: [ "$_id.year", {$year:new Date()} ]},
{$eq: [-1, {$subtract:[ "$_id.month", {$month:new Date()} ]}]}
]},
then: "$$KEEP",
else: "$$PRUNE"
}
}
},
{$project:{
_id:0,
LastMonthConsumption :1
}
}
]
);
output:{
"LastMonthConsumption" : 2.08
}
Expected output:
LastMonthConsumption - Conventional Energy/Conventional Energy*100
You can try below aggregation
db.collection2.aggregate([
{ "$group": {
"_id": { "year": { "$year": "$timeId" }, "month": { "$month": "$timeId" }},
"LastMonthConsumption": { "$sum": "$mongoStreetLightChildVo.totalConsumptionMtd" }
}},
{ "$redact": {
"$cond": {
"if": {
"$and": [
{ "$eq": ["$_id.year", { "$year": new Date() }] },
{ "$eq": [-1, { "$subtract": ["$_id.month", { "$month": new Date() }] }]
}
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$lookup": {
"from": "collection1",
"pipeline": [
{ "$match": { "type": "L" } },
{ "$count": "TOTAL_Lights" },
{ "$project": {
"ConventionalEnergy": {
"$divide": [{ "$multiply": [{ "$multiply": ["$TOTAL_Lights", 0.12] }] }, 1000]
}
}}
],
"as": "ConventionalEnergy"
}},
{ "$project": {
"_id": 0,
"totalConsumption": {
"$multiply": [
{
"$divide": [
{
"$subtract": [
"$LastMonthConsumption",
{ "$arrayElemAt": ["$ConventionalEnergy.ConventionalEnergy", 0] }
]
},
{ "$arrayElemAt": ["$ConventionalEnergy.ConventionalEnergy", 0] }
]
},
100
]
}
}}
])

Compare Size of Arrays Inside an Array of Objects

I want to find all documents where sCompetitions.length is greater than competitions.length.
Here's some sample documents document:
{
"_id" : ObjectId("59b28f432b4353d3f311dd1b"),
"name" : "Ford Focus RS 2008",
"requirements" : [
{
"rankType" : "D1",
"competitions" : [
ObjectId("59b151fd2b4353d3f3116827"),
ObjectId("59b151fd2b4353d3f3116829")
],
"sCompetitions" : [
"Rallye Monte-Carlo",
"Rally Sweden"
]
},
{
"rankType" : "A3",
"competitions" : [
ObjectId("59b151fd2b4353d3f3116f6b")
],
"sCompetitions" : [
"Rally Italia Sardegna",
"Neste Rally Finland"
]
}
]
},
{
"_id" : ObjectId("0000b28f432b4353f311dd1b"),
"name" : "Ford Focus RS 2012",
"requirements" : [
{
"rankType" : "D1",
"competitions" : [
ObjectId("59b151fd2b4353d3f3116827"),
ObjectId("59b151fd2b4353d3f3116829")
],
"sCompetitions" : [
"Rallye Monte-Carlo",
"Rally Sweden"
]
},
{
"rankType" : "A3",
"competitions" : [
ObjectId("59b151fd2b4353d3f3116f6b"),
ObjectId("59b151fd2b4353d3f3116f6b")
],
"sCompetitions" : [
"Rally Italia Sardegna",
"Neste Rally Finland"
]
}
]
}
So looking at the samples it would only return ObjectId("59b28f432b4353d3f311dd1b")
My problem is that requirements is an array by itself, so I would need to somehow iterate it
No need to "iterate". All you really need is an $anyElementTrue check after returning results from $map. And you can do this all inside a $redact action:
Model.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$anyElementTrue": {
"$map": {
"input": "$requirements",
"as": "r",
"in": {
"$gt": [
{ "$size": "$$r.sCompetitions" },
{ "$size": "$$r.competitions" }
]
}
}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
So it's a simple comparison by $size for each array element, and then if "any" of those elements is true, the document is "kept" or otherwise "pruned" from the results.

MongoDB aggregation on another aggreatation suggestions

I have a Json file imported into MongoDB. Every line on it is a user, and I have a field product, with the name of it. I know the value of every product, they are just few.
But this information is not stored on the Json.
I was able to do aggregation to retrieve the number of time that a user bought a product, but I would like to do a query to get directly the amount of money that each user spent.
This is my query:
db.source.aggregate([
{"$match": {
"$and":[
{"productName":{
"$in":[
"product2","product2","product3",
"product4","product5","product6"
]
}},
{ "$or": [
{"appID" : "nameOfAPP"},
{"appID": "NameOfAPP2"}
]}
]
}},
{ "$group": {
"_id": {
"id_user": "$id_user",
"productName": "$productName"
},
"count": { "$sum": 1}
}},
{ "$sort" : { "count": -1 } }
])
so the output is like that:
{ "_id" : { "id_user" : "user1", "productID" : "product2" }, "count" : 433 }
{ "_id" : { "id_user" : "user2", "productID" : "product1" }, "count" : 370 }
{ "_id" : { "id_user" : "user1", "productID" : "product3" }, "count" : 300 }
{ "_id" : { "id_user" : "user3", "productID" : "product6" }, "count" : 250 }
{ "_id" : { "id_user" : "user2", "productID" : "product5" }, "count" : 140 }
{ "_id" : { "id_user" : "user3", "productID" : "product4" }, "count" : 90 }
I know that product 1 costs 20$, product 2 costs 40$, product 3 costs 55$, product 4 costs -90$, product 5 costs 110$, product 6 costs 200$.
I would like to have an output like that:
{ "_id" : { "id_user" : "user1"}, "money_spent" : 600$ }
{ "_id" : { "id_user" : "user2"}, "money_spent" : 400$ }
etc
Can you help to get that result, I am new with MongoDB.
Thanks in advance.
If you cannot go to the original source data an are only working with an import then do this:
db.source.aggregate([
{"$match": {
"$and":[
{ "productName": {
"$in":[
"product1","product2","product3",
"product4","product5","product6"
]
}},
{ "$or": [
{"appID" : "nameOfAPP"},
{"appID": "NameOfAPP2"}
]}
]
}},
{ "$group": {
"_id": "$id_user",
"cost": {
"$sum": {
"$cond": [
{ "$eq": ["$_id.productId", "product1"] },
20,
{ "$cond": [
{ "$eq": ["$productName", "product2"] },
40,
{ "$cond": [
{ "$eq": [ "$productName", "product3"] },
55,
{ "$cond": [
{ "$eq": [ "$productName", "product4" ] },
-90,
{ "$cond": [
{ "$eq": [ "$productName", "product5" ] },
110,
200
]}
]}
]}
]}
}
}
}
}}
])
The $cond operator evaluates whether your field value matches the condition and places the appropriate value simply just $sum to get your result.
$cond provides a "ternary" operator or "if .. then .. else" that is used to evaluate the condition you provide in the first argument. You construct this to "cascade" where the condition evaluates to false in order to move on to the next condition to evaluate, otherwise return the value that matches your condition.
In this way your "known" values are applied as you aggregate for your expected total.