I have a mongo collection of products with attributes:
{
"_id" : ObjectId("5888a2860c001d31a1089958"),
"product_id" : "107",
"store_id" : 0,
"attributes" : [{
"key" : "m",
"value" : 21,
"label" : "Mothercare"
}, {
"key" : "sp",
"value" : 10.0,
"label" : 10.0
}, {
"key" : "pr",
"value" : 2,
"label" : "150-300"
}, {
"key" : "c",
"value" : 59,
"label" : "Category 1"
}, {
"key" : "c",
"value" : 86,
"label" : "Category 2"
}, {
"key" : "c",
"value" : 134,
"label" : "Category 3"
}, {
"key" : "c",
"value" : 1013,
"label" : "Category 4"
}, {
"key" : "c",
"value" : 1063,
"label" : "Category 5"
}, {
"key" : "c",
"value" : 1073,
"label" : "Category 6"
}, {
"key" : "13",
"value" : 270,
"label" : "Brown"
}, {
"key" : "18",
"value" : 125,
"label" : "Girl"
}, {
"key" : "19",
"value" : 298,
"label" : "0-3 month"
}, {
"key" : "19",
"value" : 299,
"label" : "3-6 month"
}, {
"key" : "19",
"value" : 300,
"label" : "6-9 month"
}, {
"key" : "19",
"value" : 301,
"label" : "9-12 month"
}]
}
I need to find fast way for get count of all attributes in collection. I have tried to use MapReduce:
function map() {
var max = this.attributes.length;
var key = {};
for (var i = 0; i < max; i++) {
key = {
key: this.attributes[i]['key'],
value: this.attributes[i]['value'],
}
emit(key, {count: 1});
}
}
function reduce(key, values) {
var sum = 0;
values.forEach(function(value) {
sum += value['count'];
});
return {count: sum};
};
But it very slow:
timeMillis=2420
counts={ "input" : 18963, "emit" : 221232, "reduce" : 7341, "output" : 1289 }
How can I find the quantity of all attributes faster? I need it for product filter. Maybe I must use other collection structure?
I not need to find total count of attributes, I need to find count of each attribute, for example:
{ "key" : "c", "value" : 59 } has 2345 products
{ "key" : "m", "value" : 21 } has 258 products
Running the following pipeline will give you the desired result:
db.collection.aggregate([
{ "$unwind": "$attributes" },
{
"$group": {
"_id": {
"key": "$attributes.key",
"value": "$attributes.value"
},
"counts": { "$sum": 1 }
}
}
])
For a more efficient query, use the aggregation framework. Consider running a pipeline with $project to get the number of attributes per document using the $size operator on the attributes array and then a final
$group pipeline where you can specify an _id value of null to calculate accumulated values for all the input documents as a whole and calculate the total counts using $sum as follows:
db.collection.aggregate([
{
"$project": {
"counts": {
"$size": "$attributes"
}
}
},
{
"$group": {
"_id": null,
"counts": { "$sum": "$counts" }
}
}
])
The above will return the total number of attributes of ALL products in a collection.
If you want to use the count of the attributes to filter a product, then consider using the $redact pipeline as:
var attributeCount = 12; // for example
db.collection.aggregate([
{
"$redact": {
"$cond": [
{ "$eq": [ { "$size": "$attributes" }, attributeCount ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
This is equivalent to a combination of a $project and $match pipeline albeit you don't have to specify all the fields in the $project pipeline, as in the following:
db.collection.aggregate([
{
"$project": {
"product_id": 1,
"store_id": 1,
"$attributes": 1,
"counts": {
"$size": "$attributes"
}
}
},
{ "$match": { "counts": { "$gte": attributeCount } } }
])
To get total count of attributes by key value pair can try this query.
db.collectionName.aggregate([
{$unwind:{"$attributes"}}
{$group: {
_id: {"key": "$attributes.key","value": "$attributes.value"},
count: { $sum: 1 }
}
},
{$project:{
key:"$_id.key",
value:"$_id.value",
count:1
}
}
])