Use $gt operator to return document values among all the objects - mongodb

This is one of many similar objects in shopping list collection. How do I do a query to get the list of only the "name" of people buying more than 2 "Noodles"?
Please help me figure this out, thanks in advance.
I assume this should have the $gt operator but I am not sure how to execute it correctly.
{
"_id" : ObjectId("591422529f75f9119575c1d8"),
"name" : "Hisham",
"age" : 20,
"address" : {
"house" : "HomeName",
"street" : "Fairyland",
"city" : "Faketon",
"pincode" : 000000
},
"itemlist" : [
{
"iname" : "Soap",
"quantity" : 2,
"price" : 10,
"rate" : 20,
"itemID" : "1"
},
{
"iname" : "Mirror",
"quantity" : 1,
"price" : 600,
"rate" : 600,
"itemID" : "4"
},
{
"iname" : "Noodles",
"quantity" : 4,
"price" : 50,
"rate" : 200,
"itemID" : "5"
},
{
"iname" : "Plug",
"quantity" : 2,
"price" : 50,
"rate" : 100,
"itemID" : "6"
}
]
}

you can achieve this with the aggregation framework like this :
db.collection.aggregate([
{
$unwind:"$itemlist"
},
{
$match:{
"itemlist.iname":"Noodles"
}
},
{
$group:{
_id:"$itemlist.iname",
name:{
$first:"$name"
},
count:{
$sum:1
}
}
},
{
$match:{
count:{
$gte:2
}
}
}
])
How it works:
unwind the itemlist array with $unwind
keep only Noodles item
count occurence of Noodles using $group
keep only document where count >= 2

You can select the all documents that match your criteria using the $elemMatch operator in the $match. From there all you need is a $group stage.
db.collection.aggregate([
{ "$match": {
"itemlist": {
"$elemMatch": {
"quantity": { "$gt": 2 },
"iname": "Noodles"
}
}
}},
{ "$group": { "_id": null, "names": { "$push": "$name" } } }
])

Related

How to use $unwind and $match with MongoDB?

I have a document of the following format:
{
"P": {
"Workspaces": [
{
"Key": "Value1",
"Size": 2.27,
"Status": 'something'
},
{
"Key": "Value2",
"Size": 3.27,
"Status": 'somethingelse'
}
]
}
}
The following query returns the average correctly.
db.collection.aggregate([
{ $unwind: "$P.Workspaces" },
{ $group: { _id: "$P.Workspaces.Key", average: { $avg: "$P.Workspaces.Size" } } }
])
I am trying to add a match to filter the status as shown below. However I am not getting no result even though there are documents with matching status. I am trying to filter the results before taking the average. Am I missing something here?
db.collection.aggregate([
{ $unwind: "$P.Workspaces" },
{ $match: { "P.Workspaces.Status":'something'}},
{ $group: { _id: "$P.Workspaces.Key", average: { $avg: "$P.Workspaces.Size" } } }
])
db.articles.aggregate(
[ { $match : { author : "dave" } } ]
);
The examples use a collection named articles with the following documents:
{ "_id" : ObjectId("512bc95fe835e68f199c8686"), "author" : "dave", "score" : 80, "views" : 100 } { "_id" : ObjectId("512bc962e835e68f199c8687"), "author" : "dave", "score" : 85, "views" : 521 } { "_id" : ObjectId("55f5a192d4bede9ac365b257"), "author" : "ahn", "score" : 60, "views" : 1000 } { "_id" : ObjectId("55f5a192d4bede9ac365b258"), "author" : "li", "score" : 55, "views" : 5000 } { "_id" : ObjectId("55f5a1d3d4bede9ac365b259"), "author" : "annT", "score" : 60, "views" : 50 }

How to find minimum value in aggregate $group

I have below collection structure and I want to find minimum score for each student.
>db.students.findOne()
{
"_id" : 0,
"name" : "aimee Zank",
"scores" : [
{
"type" : "exam",
"score" : 1.463179736705023
},
{
"type" : "quiz",
"score" : 11.78273309957772
},
{
"type" : "homework",
"score" : 6.676176060654615
},
{
"type" : "homework",
"score" : 35.8740349954354
}
]
}
I use below aggregate command
db.students.aggregate([
{
$group: {_id: "$_id" , min: {$min: '$scores.score'}}
}
])
below is the output:
{ "_id" : 199, "min" : [ 82.11742562118049, 49.61295450928224, 28.86823689842918, 5.861613903793295 ] }
{ "_id" : 198, "min" : [ 11.9075674046519, 20.51879961777022, 55.85952928204192, 64.85650354990375 ] }
{ "_id" : 95, "min" : [ 8.58858127638702, 88.40377630359677, 25.71387474240768, 23.73786528217532 ] }
{ "_id" : 11, "min" : [ 78.42617835651868, 82.58372817930675, 87.49924733328717, 15.81264595052612 ] }
{ "_id" : 94, "min" : [ 6.867644836612586, 63.4908039680606, 85.41865347441522, 26.82623527074511 ] }
it returns all scores for each student instead of the minimum one. What wrong with my query command? I am using mongo 3.4.
After some searching, I found that the solution is to add $unwind on scores.score. The complete command is:
stus = db.students.aggregate([
{
"$unwind": "$scores"
},
{
$group: {_id: "$_id" , minScore: {$min: '$scores.score'}}
}
])

Get count of product attributes from MongoDB

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
}
}
])

MongoDb aggregation framework value of a field where max another field

I have a collection that has records looking like this:
"_id" : ObjectId("550424ef2f44472856286d56"), "accountId" : "123",
"contactOperations" :
[
{ "contactId" : "1", "operation" : 1, "date" : 500 },
{ "contactId" : "1", "operation" : 2, "date" : 501 },
{ "contactId" : "2", "operation" : 1, "date" : 502 }
]
}
I want to know the latest operation number that has been applied on a certain contact.
I'm using the aggregation framework to first unwind the contactOperations and then grouping by accountId and contactOperations.contactId and max contactOperations.date.
aggregate([{$unwind : "$contactOperations"}, {$group : {"_id":{"accountId":"$accountId", "contactId":"$contactOperations.contactId"}, "date":{$max:"$contactOperations.date"} }}])
The result I get is:
"_id" : { "accountId" : "123", "contactId" : "2" }, "time" : 502 }
"_id" : { "accountId" : "123", "contactId" : "1" }, "time" : 501 }
Which seems correct so far, but I also need the contactOperations.operation field that was recorded with $max date. How can I select that?
You have to sort the unwind values then apply $last operator to get operation for max date. Hope this query will solve your problem.
aggregate([
{
$unwind: "$contactOperations"
},
{
$sort: {
"date": 1
}
},
{
$group: {
"_id": {
"accountId": "$accountId",
"contactId": "$contactOperations.contactId"
},
"date": {
$max: "$contactOperations.date"
},
"operationId": {
$last: "$contactOperations.operation"
}
}
}
])

How to ensure grouping via two separate criteria

Expanded from How to average the summed up values in mongodb?
Using MongoDB 2.4.8,
I have the following records
{
"category" : "TOYS",
"price" : 12,
"status" : "online",
"_id" : "35043"
}
{
"category" : "TOYS",
"price" : 13,
"status" : "offline",
"_id" : "35044"
}
{
"category" : "TOYS",
"price" : 22,
"status" : "online",
"_id" : "35045"
}
{
"category" : "BOOKS",
"price" : 13,
"status" : "offline",
"_id" : "35046"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "online",
"_id" : "35047"
}
{
"category" : "TOYS",
"price" : 19,
"status" : "unavailable",
"_id" : "35048"
}
{
"category" : "BOOKS",
"price" : 10,
"status" : "unavailable",
"_id" : "35049"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "unavailable",
"_id" : "35050"
}
I want to find the average price of all categories whose status is online OR offline and total price within a category is more than 50.
Toys offline and Toys online are considered two separate categories.
I adapted the answer given.
db.items.aggregate([
{$match:
{
$or: [
{status:"online"},
{status:"offline"}
]
}
},
{$group :
{
_id: "$category",
total_price: {$sum:"$price"},
}
},
{$match:
{
total_price:{$gt:50}
}
},
{$group :
{
_id: "1",
avg_price: {$avg:"$total_price"},
}
},
]);
But I believe this query I adapted grouped categories of the same name together which is not what I am looking for.
If online and offline are the only values for status, you can remove the initial $match step. If it is needed, it would be more appropriate to use the $in operator as these values could be found in the same index (if one existed).
I think the only step you are missing is that you can $group by multiple fields (i.e. category and status):
db.items.aggregate(
// If 'online' and 'offline' are the only possible status values, this may be unnecessary
{ $match: {
'status' : { $in: [ 'online', 'offline' ] }
}},
// Group by category & status
{ $group: {
_id: { category: "$category", status: "$status" },
total_price: { $sum: "$price" },
}},
// Only find groups where total_price is > 50
{ $match: {
total_price: { $gt:50 }
}},
// Find the average price for the group
{ $group : {
_id: null,
avg_price: {$avg:"$total_price"},
}}
)