Consider the following MongoDB \"recipes\" collection:
{
\"title\" : \"Macaroni and Cheese\",
\"ingredients\" : [
{ \"name\" : \"noodles\", \"qty\" :
Here is the full aggregate pipeline you were looking for:
db.recipes.aggregate([
{$unwind:"$ingredients"},
{$group:{_id:"$ingredients.name",
quantity:{$first:"$ingredients.qty"},
recipes:{$push:"$title"},
total:{$sum:1}
}
},
{$project:{_id:0,ingredient:"$_id", total:1, quantity:1, recipes:1}
])
This unwinds the arrays, groups by ingredient, adds them up to see how many recipes need them, keeps the quantity fields which is going to be the same in your use case, and adds a list of recipes it's used in. Last step renames the grouped on field to "ingredient".
Result:
{ "quantity" : "1 lb", "recipes" : [ "Pound Cake", "Dough" ], "total" : 2, "ingredient" : "flour" }
{ "quantity" : "2 c", "recipes" : [ "Dough" ], "total" : 1, "ingredient" : "water" }
{ "quantity" : "1 lb", "recipes" : [ "Pound Cake" ], "total" : 1, "ingredient" : "sugar" }
{ "quantity" : "1 c", "recipies" : [ "Macaroni and Cheese" ], "total" : 1, "ingredient" : "cheese" }
{ "quantity" : "2 tbl", "recipes" : [ "Macaroni and Cheese", "Pound Cake", "Dough" ], "total" : 3, "ingredient" : "butter" }
{ "quantity" : "2 c", "recipes" : [ "Macaroni and Cheese" ], "total" : 1, "ingredient" : "noodles" }
The following query would give you the bare list minus any duplicates:
db.recipes.aggregate([{$unwind:"$ingredients"},{$group:{_id:"$ingredients.name"}}])
More work would be necessary to add the quantities. It would be easier if the unit for the quantities was specified separately.
Use distinct
to find an array of distinct values for ingredients.name
db.recipes.distinct('ingredients.name')
yields [ "butter", "cheese", "noodles", "flour", "sugar", "water" ]
A funny way of doing it in the mongoshell using foreach
operator
var list = [];
db.recepies.find(
{},
{'ingredients.name' : 1, _id : 0}
).forEach(function(doc){
var ingredients = doc.ingredients;
for (var i=0; i< ingredients.length; i++){
var ingredient = ingredients[i].name;
if (list.indexOf(ingredient) == -1){
list.push(ingredient)
}
}
});
after this list
will contain all the elements. P.S. I am sure this is also possible with aggregation framework.