how to apply $setunion for this aggregation? - mongodb

Here my db is:
{
"_id" : ObjectId("5d28667fb0adb622b905ccd2"),
"requestedDate" : ISODate("2019-07-12T10:52:47.711Z"),
"requestType" : "A",
"isRequestSuccess" : false,
"responseDate" : ISODate("2019-07-12T10:53:19.213Z"),
"__v" : 0
},
{
"_id" : ObjectId("5d28667fb0adb622b905ccd2"),
"requestedDate" : ISODate("2019-07-12T10:52:47.711Z"),
"requestType" : "C",
"isRequestSuccess" : false,
"responseDate" : ISODate("2019-07-12T10:53:19.213Z"),
"__v" : 0
},
{
"_id" : ObjectId("5d28667fb0adb622b905ccd2"),
"requestedDate" : ISODate("2019-07-12T10:52:47.711Z"),
"requestedType" : "A",
"isRequestSuccess" : false,
"responseDate" : ISODate("2019-07-12T10:53:19.213Z"),
"__v" : 0
}
I need to get the values by each requestType and also isRequestSuccess: sucess or failure wise.
[
{ requestedType: "A", isRequestSuccess: 2, isRequestFalse: 1 },
{ requestedType: "C", isRequestSuccess: 1, isRequestFalse: 3 }
]
How can I get those values?

db.getCollection('test').aggregate({
"$group": {
"_id": "$requestedType",
"isRequestSuccess": {
$sum: {
$cond: ["$isRequestSuccess", 1, 0]
}
},
"isRequestFalse": {
$sum: {
$cond: ["$isRequestSuccess", 0, 1]
}
}
}
})

Related

Filter mongodb from custom query objects as input

I am trying to filter my search results by id and version from a list of input. If the version is not provided, then get the latest for that ID. the below query works when I have both id and version. However, for id:2, I need only the latest to be returned.
db.mycollection.find({
$or: [
{
id: 1,
version: 1
},
{
id : 1,
version: 2
},
{
id : 2,
version: {$max: "$version"} // get the latest version if not provided
}
]})
I get the below error
{
"message" : "unknown operator: $max",
"ok" : 0,
"code" : 2,
"codeName" : "BadValue",
"name" : "MongoError"}
The aggregation query using facets can get documents for the two different conditions:
db.myCollection.aggregate( [
{ $facet: {
with_version: [
{ $match: { $or: [ { id: 1, version: 1 }, { id: 3, version: 2 } ] } },
],
without_version: [
{ $match: { id: 2 } },
{ $sort: { version: -1 } },
{ $limit: 1 },
],
} },
{ $project: { docs: { $concatArrays: [ "$with_version", "$without_version" ] } } },
{ $unwind: "$docs" },
{ $project: { _id: "$docs._id", id: "$docs.id", version: "$docs.version" } },
] )
Using the following documents as input:
{ "_id" : "a", "id" : 1, "version" : 1 }
{ "_id" : "b", "id" : 2, "version" : 1 }
{ "_id" : "c", "id" : 3, "version" : 2 }
{ "_id" : "d", "id" : 2, "version" : 2 }
{ "_id" : "e", "id" : 4, "version" : 2 }
{ "_id" : "f", "id" : 3, "version" : 1 }
{ "_id" : "g", "id" : 3, "version" : 0 }
{ "_id" : "h", "id" : 2, "version" : 0 }
The result shows the matching documents for { id: 1, version: 1 }, { id: 3, version: 2 }, and the maximum version number for id: 2.
{ "_id" : "a", "id" : 1, "version" : 1 }
{ "_id" : "c", "id" : 3, "version" : 2 }
{ "_id" : "d", "id" : 2, "version" : 2 }

mongodb aggregate sum item as nested data

Here is my some sample data in collection sale
[
{group:2, item:a, qty:3 },
{group:2, item:b, qty:3 },
{group:2, item:b, qty:2 },
{group:1, item:a, qty:3 },
{group:1, item:a, qty:5 },
{group:1, item:b, qty:5 }
]
and I want to query data like below and sort the popular group to the top
[
{ group:1, items:[{name:'a',total_qty:8},{name:'b',total_qty:5} ],total_qty:13 },
{ group:2, items:[{name:'a',total_qty:3},{name:'b',total_qty:5} ],total_qty:8 },
]
Actually we can loop in server script( php, nodejs ...) but the problem is pagination. I cannot use skip to get the right result.
The following query can get us the expected output:
db.collection.aggregate([
{
$group:{
"_id":{
"group":"$group",
"item":"$item"
},
"group":{
$first:"$group"
},
"item":{
$first:"$item"
},
"total_qty":{
$sum:"$qty"
}
}
},
{
$group:{
"_id":"$group",
"group":{
$first:"$group"
},
"items":{
$push:{
"name":"$item",
"total_qty":"$total_qty"
}
},
"total_qty":{
$sum:"$total_qty"
}
}
},
{
$project:{
"_id":0
}
}
]).pretty()
Data set:
{
"_id" : ObjectId("5d84a37febcbd560107c54a7"),
"group" : 2,
"item" : "a",
"qty" : 3
}
{
"_id" : ObjectId("5d84a37febcbd560107c54a8"),
"group" : 2,
"item" : "b",
"qty" : 3
}
{
"_id" : ObjectId("5d84a37febcbd560107c54a9"),
"group" : 2,
"item" : "b",
"qty" : 2
}
{
"_id" : ObjectId("5d84a37febcbd560107c54aa"),
"group" : 1,
"item" : "a",
"qty" : 3
}
{
"_id" : ObjectId("5d84a37febcbd560107c54ab"),
"group" : 1,
"item" : "a",
"qty" : 5
}
{
"_id" : ObjectId("5d84a37febcbd560107c54ac"),
"group" : 1,
"item" : "b",
"qty" : 5
}
Output:
{
"group" : 2,
"items" : [
{
"name" : "b",
"total_qty" : 5
},
{
"name" : "a",
"total_qty" : 3
}
],
"total_qty" : 8
}
{
"group" : 1,
"items" : [
{
"name" : "b",
"total_qty" : 5
},
{
"name" : "a",
"total_qty" : 8
}
],
"total_qty" : 13
}
You need to use $group aggregation with $sum and $push accumulator
db.collection.aggregate([
{ "$group": {
"_id": "$group",
"items": { "$push": "$$ROOT" },
"total_qty": { "$sum": "$qty" }
}},
{ "$sort": { "total_qty": -1 }}
])

how to show 0 for week when no record is matching that week in $week mongodb query

My collection looks like below with details
/* 1 createdAt:6/13/2018, 5:17:07 PM*/
{ "_id" : ObjectId("5b21043b18f3bc7c0be3414c"),
"Number" : 242,
"State" : "2",
"City" : "3",
"Website" : "",
"Contact_Person_Name" : "Ajithmullassery",
"CreatedById" : "Admin",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-13T17:17:07.313+05:30"),
"CreatedOn" : ISODate("2018-06-13T17:17:07.313+05:30")
},
/* 2 createdAt:6/13/2018, 6:45:42 PM*/
{
"_id" : ObjectId("5b2118fe18f3bc7c0be3415b"),
"Number" : 243,
"State" : "1",
"City" : "143",
"Website" : "",
"Contact_Person_Name" : "sachitkumar",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-13T18:45:42.590+05:30"),
"CreatedOn" : ISODate("2018-06-13T18:45:42.590+05:30")
},
/* 3 createdAt:6/18/2018, 5:34:33 PM*/
{
"_id" : ObjectId("5b279fd118f3bc7c0be34166"),
"Number" : 244,
"State" : "0",
"City" : "8",
"Website" : "",
"Contact_Person_Name" : "Akshay",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-18T17:34:33.270+05:30"),
"CreatedOn" : ISODate("2018-06-18T17:34:33.270+05:30")
},
/* 4 createdAt:6/20/2018, 1:02:21 PM*/
{
"_id" : ObjectId("5b2a030518f3bc7c0be3416d"),
"Number" : 245,
"State" : "5",
"City" : "6",
"Website" : "",
"Contact_Person_Name" : "Dr DS Mithra",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"FacilityID" : "594387f5e2de7be83be5d5f1",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-20T13:02:21.887+05:30"),
"CreatedOn" : ISODate("2018-06-20T13:02:21.887+05:30")
},
/* 5 createdAt:6/20/2018, 1:08:58 PM*/
{
"_id" : ObjectId("5b2a049218f3bc7c0be3416e"),
"Number" : 245,
"State" : "5",
"City" : "6",
"Website" : "",
"Contact_Person_Name" : "Ramaswamy Manickam",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-20T13:08:58.040+05:30"),
"CreatedOn" : ISODate("2018-06-20T13:08:58.040+05:30")
}
I have the query like below
db.collectionName.aggregate([
//where query
{ "$match": { $and:[{CreatedOn:{$lte:ISODate("2018-07-14T13:59:08.266+05:30")}},{CreatedOn:{$gte:ISODate("2018-06-10T13:59:08.266+05:30")}}] } },
//distinct column
{
"$group": {
_id: {$week: '$CreatedOn'},
documentCount: {$sum: 1}
}
}
])
The query will return the weeknumber and number of documents created as below
/* 1 */
{
"_id" : 26,
"documentCount" : 1
},
/* 2 */
{
"_id" : 25,
"documentCount" : 1
},
/* 3 */
{
"_id" : 24,
"documentCount" : 9
},
/* 4 */
{
"_id" : 23,
"documentCount" : 2
}
In above _id is the weeknumber. If in case in above results weekNumber : 23 no records are created then the query gives only 3 records removing the "_id":23.
How to get the records with documentcount as zero when there is no records created.
Like in above example when no records for _id: 23 should get like below
/* 4 */
{
"_id" : 23,
"documentCount" : 0
}
As $week can return a value between 0 and 53 I assume you expect 54 documents as a result with 0 or non-zero values for documentCount. To achieve that you should collect all your documents into one ($group-ing by null) and then generate the output.
To generate a range of numbers you can use $range operator and then you can generate the output using $map. To transform an array of documents into multiple docs you can use $unwind.
db.collectionName.aggregate([
//where query
{ "$match": { $and:[{CreatedOn:{$lte:ISODate("2018-07-14T13:59:08.266+05:30")}},{CreatedOn:{$gte:ISODate("2018-06-10T13:59:08.266+05:30")}}] } },
//distinct column
{
"$group": {
_id: {$week: '$CreatedOn'},
documentCount: {$sum: 1}
}
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: { $range: [ {$week:ISODate("2018-06-10T13:59:08.266+05:30")}, {$week:ISODate("2018-07-14T13:59:08.266+05:30")}]},
as: "weekNumber",
in: {
$let: {
vars: { index: { $indexOfArray: [ "$docs._id", "$$weekNumber" ] } },
in: {
$cond: {
if: { $eq: [ "$$index", -1 ] },
then: { _id: "$$weekNumber", documentCount: 0 },
else: { $arrayElemAt: [ "$docs", "$$index" ] }
}
}
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
newRoot: "$docs"
}
}
])
Using $indexOfArray to check if array of current docs contains the document (-1 otherwise) and $arrayElemAt to get existing document from docs. Last step ($replaceRoot) is just to get rid of one level of nesting (docs). Outputs:
{ "_id" : 0, "documentCount" : 0 }
{ "_id" : 1, "documentCount" : 0 }
{ "_id" : 2, "documentCount" : 0 }
...
{ "_id" : 22, "documentCount" : 0 }
{ "_id" : 23, "documentCount" : 2 }
{ "_id" : 24, "documentCount" : 9 }
{ "_id" : 25, "documentCount" : 1 }
{ "_id" : 26, "documentCount" : 1 }
{ "_id" : 27, "documentCount" : 0 }
...
{ "_id" : 52, "documentCount" : 0 }
{ "_id" : 53, "documentCount" : 0 }
You can easily customize returned results modifying the input of $map stage. For instance you can pass an array of consts like input: [21, 22, 23, 24] as well.
EDIT: To get the weeks between specified dates you can use $week for start and end date to get the numbers.

Grouping over multiple fields in MongoDb

How would I go about grouping over multiple fields? I need to get a unique count for case insensitive true over multiple independent documents.
I've looked at both map/reduce and aggregation and I don't quite know what would be the best approach.
Lets say I have the following data in my collection:
/* 0 */
{
"_id" : ObjectId("****"),
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "true",
"A" : "true",
"B" : "true",
"C" : "",
}
}
/* 1 */
{
"_id" : ObjectId("****"),
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "true",
"A" : "true",
"B" : "true",
"C" : "",
"D" : "TRUE"
}
}
/* 2 */
{
"_id" : ObjectId("****"),
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "true",
"A" : "true",
"B" : "TRUE",
"C" : "",
"D" : "false"
}
}
/* 3 */
{
"_id" : ObjectId("****"),
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "false",
"A" : "true",
"B" : "false",
"D" : "true"
}
}
I would like to output the following data, formatting is not important:
isMail : 3
A : 4
B : 3
C : 0
D : 2
Total : 4
Using the conditional operator $cond to map "true" to 1 and anything else to 0, you might achieve the desired result. This is only complicated by the fact your "boolean" values are in fact strings, and that you have case variation on the "true" value -- that's why I use $toLower in the code below:
db.test.sample.aggregate([
{
$group: { _id:null,
isMail: { $sum: { $cond: [{$eq: [{$toLower:"$Data.isMail"}, "true"]}, 1, 0] }},
A: { $sum: { $cond: [{$eq: [{$toLower:"$Data.A"}, "true"]}, 1, 0] }},
B: { $sum: { $cond: [{$eq: [{$toLower:"$Data.B"}, "true"]}, 1, 0] }},
C: { $sum: { $cond: [{$eq: [{$toLower:"$Data.C"}, "true"]}, 1, 0] }},
D: { $sum: { $cond: [{$eq: [{$toLower:"$Data.D"}, "true"]}, 1, 0] }},
total: { $sum: 1 },
}
},
{
$project: {
_id: 0,
A: 1, B: 1, C:1, D:1, total:1, isMail:1,
}
}
])
Producing:
{ "isMail" : 3, "A" : 4, "B" : 3, "C" : 0, "D" : 2, "total" : 4 }
If you could change the schema design so that the data keys become the values, it would go a long way in making it easier for you to do some aggregation operations on the data. A better shchema would look like this:
{
"_id" : ObjectId("5548de01180e84997293903f"),
"IsPartOfBatch" : false,
"Data" : [
{
"key" : "isMail",
"value" : true
},
{
"key" : "A",
"value" : true
},
{
"key" : "B",
"value" : true
},
{
"key" : "C",
"value" : false
},
{
"key" : "D",
"value" : false
}
]
}
Let's use the sample data set you provided in your question:
db.test.insert([
{
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "true",
"A" : "true",
"B" : "true",
"C" : ""
}
},
{
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "true",
"A" : "true",
"B" : "true",
"C" : "",
"D" : "TRUE"
}
},
{
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "true",
"A" : "true",
"B" : "TRUE",
"C" : "",
"D" : "false"
}
},
{
"IsPartOfBatch" : false,
"Data" : {
"isMail" : "false",
"A" : "true",
"B" : "false",
"D" : "true"
}
}
]);
To change the schema so that it follows the above recommended structure, use the following code snippet (performance may be slow over very large datasets):
db.test.find({ "Data.isMail": { $type : 2 } }).forEach(function (doc){
var data = [];
if (doc.Data) {
for(key in doc.Data) {
var isTrueSet = (doc.Data[key] === "true" || doc.Data[key] === "TRUE")
var obj = {};
obj["key"] = key;
obj["value"] = isTrueSet;
data.push(obj);
};
}
doc.Data = data;
db.test.save(doc);
});
A simple db.test.findOne() query will give the result:
{
"_id" : ObjectId("5548de01180e84997293903f"),
"IsPartOfBatch" : false,
"Data" : [
{
"key" : "isMail",
"value" : true
},
{
"key" : "A",
"value" : true
},
{
"key" : "B",
"value" : true
},
{
"key" : "C",
"value" : false
},
{
"key" : "D",
"value" : false
}
]
}
Now you can use aggregation framework to get the counts of the keys with true values:
db.test.aggregate([
{
"$unwind": "$Data"
},
{
"$project": {
"_id": 0,
"key": "$Data.key",
"isTrue": {
"$cond": [{ "$eq": [ "$Data.value", true ] }, 1, 0]
}
}
},
{
"$group": {
"_id": "$key",
"count": {
"$sum": "$isTrue"
}
}
}
])
Output
/* 0 */
{
"result" : [
{
"_id" : "D",
"count" : 2
},
{
"_id" : "C",
"count" : 0
},
{
"_id" : "B",
"count" : 3
},
{
"_id" : "A",
"count" : 4
},
{
"_id" : "isMail",
"count" : 3
}
],
"ok" : 1
}
You can then further modify the result using native JavaScript functions as MongoDB's aggregation framework cannot project the field values as the keys thus you will have to rely on JS to do this:
var pipeline = [
{
"$unwind": "$Data"
},
{
"$project": {
"_id": 0,
"key": "$Data.key",
"isTrue": {
"$cond": [{ "$eq": [ "$Data.value", true ] }, 1, 0]
}
}
},
{
"$group": {
"_id": "$key",
"count": {
"$sum": "$isTrue"
}
}
}],
agg = db.test.aggregate(pipeline),
obj = {},
result = [];
agg.forEach(function (doc){
obj[doc._id] = doc.count;
result.push(obj);
});

Use field value as key

I am doing this query
db.analytics.aggregate([
{
$match: {"event":"USER_SENTIMENT"}
},
{ $group: {
_id: {brand:"$data.brandId",sentiment:"$data.sentiment"},
count: {$sum : 1}
}
},
{ $group: {
_id: "$_id.brand",
sentiments: {$addToSet : {sentiment:"$_id.sentiment", count:"$count"}}
}
}
])
Which generates that :
{
"result" : [
{
"_id" : 57,
"sentiments" : [
{
"sentiment" : "Meh",
"count" : 4
}
]
},
{
"_id" : 376,
"sentiments" : [
{
"sentiment" : "Meh",
"count" : 1
},
{
"sentiment" : "Happy",
"count" : 1
},
{
"sentiment" : "Confused",
"count" : 1
}
]
}
],
"ok" : 1
}
But What I want is that :
[
{
"_id" : 57,
"Meh" : 4
},
{
"_id" : 376,
"Meh" : 1,
"Happy" : 1,
"Confused" : 1
}
]
Any idea on how to transform that? The blocking point for me is to transform a value into a key.