MongoDB transforming data using aggregation - mongodb

I have the following data in a collection.
[
{name: "a", status: 1},
{name: "a", status: 2},
{name: "a", status: 3},
{name: "b", status: 1},
{name: "b", status: 4},
{name: "c", status: 1},
{name: "c", status: 1},
{name: "c", status: 5}
]
I want it to be transformed as:
{
names: ["a", "b", "c"],
statuses: [1, 2, 3, 4, 5],
count: [
[1, 1, 1, 0, 0],
[1, 0, 0, 1, 0],
[2, 0, 0, 0, 1]
]
}
Explanation:
names will have the unique list of name attribute
statuses will have the unique list of status attribute
Count contains array of count of status per name
For example the first count array is [1, 1, 1, 0, 0]
Which means:
There is only one status:1 for the name a
There is only one status:2 for the name a and so on..
If you take the 3rd array [2, 0, 0, 0, 1]
The name c has status:1 two times
There is no 2 or 3 or 4 but it has one 5
Can you suggest the corresponding aggregation function or equivalent?
Edit:
I just have this so far:
db.collection1.aggregate([
{
$group: {
_id: {environment: "$name", status: "$status"},
count:{$sum: 1}
}
}
])
Thank you

Related

Mongodb : how to use aggregate to build standings on nested fields

This is my collection :
[
{userId: "u1", data: { score1: 1, score2: 2, score3: 3 }, day: 1},
{userId: "u1", data: { score1: 1, score2: 0, score3: 0 }, day: 2},
{userId: "u1", data: { score1: 5, score2: 3, score3: 2 }, day: 3},
{userId: "u2", data: { score1: 2, score2: 5, score3: 1 }, day: 1},
{userId: "u2", data: { score1: 1, score2: 1, score3: 6 }, day: 2},
{userId: "u2", data: { score1: 3, score2: 5, score3: 3 }, day: 3},
{userId: "u3", data: { score1: 4, score2: 1, score3: 1 }, day: 1},
{userId: "u3", data: { score1: 0, score2: 1, score3: 1 }, day: 2},
{userId: "u3", data: { score1: 0, score2: 1, score3: 10 }, day: 3}
]
I would like to build the following leaderboards tables :
{
score1: [
{"u1": 7}, // sum of all score1 for u1
{"u2": 6}, // sum of all score1 for u2
{"u3": 4}, // sum of all score1 for u3
],
score2: [
{"u2": 11}, // sum of all score2 for u2
{"u1": 5}, // sum of all score2 for u1
{"u3": 3}, // sum of all score2 for u3
],
score3: [
{"u3": 12}, // sum of all score3 for u3
{"u2": 10}, // sum of all score3 for u2
{"u1": 5}, // sum of all score3 for u1
],
}
So far I can group by userId and compute the aggregate of each score for the 3 of them :
db.myCollection.aggregate([
{
$group: {
_id: "$userId",
score1: { $sum: "$score1" },
score2: { $sum: "$score2" },
score3: { $sum: "$score3" }
}
}
])
Which gives me :
[
{
_id: "u1",
score1: 7,
score2: 5,
score3: 5
},
{
_id: "u2",
score1: 6,
score2: 11,
score3: 10
},
{
_id: "u3",
score1: 4,
score2: 3,
score3: 12
},
]
How can I extract each type of score and build their corresponding leaderboard ?
Thanks in advance.
I would first use $objectToArray on the data field and $unwind it so each document has 1 user and 1 score. Then group by userId and data.k (which will contain "score1", "score2", etc.) and compute sum. Then regroup by score name and push an object with k:userId, v:<score> to an array. Then group once more on null and push k:scoreName, v:<object with user scores> to an array. Finally $arrayToObject to convert that array to the object you want:
db.collection.aggregate([
{$addFields: {data: {$objectToArray: "$data"}}},
{$unwind: "$data"},
{$group: {
_id: {userId: "$userId", scoreName: "$data.k"},
score: {$sum:"$data.v"}
}},
{$group: {
_id:"$_id.scoreName",
data:{$push:{k:"$_id.userId", v:"$score"}}
}},
{$group: {
_id: null,
scores:{$push:{k:"$_id", v:{$arrayToObject:"$data"}}}
}},
{$replaceRoot:{newRoot:{$arrayToObject:"$scores"}}}
])
Playground

Query all array element is in another array

I would like to query documents from a collection where all elements in an array match a condition: both the name field and the quantity should be in and above the minQuantity in the "currentStatus" array.
In this example, I would like to retrieve the documents _id : 1 and 4
Collection = [
{
_id: 1,
conditions: [ {name: 'product1', minQuantity: 30},
{name: 'product2', minQuantity: 10} ]
},
{
_id: 2,
conditions: [ {name: 'product1', minQuantity: 50},
{name: 'product2', minQuantity: 10} ]
},
{
_id: 3,
conditions: [ {name: 'product1', minQuantity: 10},
{name: 'product2', minQuantity: 10},
{name: 'product3', minQuantity: 10} ]
}
{
_id: 4,
conditions: [ {name: 'product1', minQuantity: 10} ]
}
]
currentStatus = [
{name: 'product1', currentQuantity: 40},
{name: 'product2', currentQuantity: 20},
]

How to do mongodb inner join with nested array?

Warehouses schema:
{_id: 1, name: 'A'}
{_id: 2, name: 'B'}
{_id: 3, name: 'C'}
Stocks schema:
{_id: 11, productId: 1, instock: [{warehouse: 'A', qty: 20}, {warehouse: 'B', qty: 5}, {warehouse: 'C', qty: 8}]
{_id: 12, productId: 2, instock: [{warehouse: 'A', qty: 30}]
I am new to MongoDB, but will like to have one row per record to show products' available qty in each of A,B,C warehouses:
Desired array output:
instock: [
{_id: 11, productId: 1, warehouse: 'A', qty: 20},
{_id: 11, productId: 1, warehouse: 'B', qty: 5},
{_id: 11, productId: 1, warehouse: 'C', qty: 8},
{_id: 12, productId: 2, warehouse: 'A', qty: 30},
{_id: 12, productId: 2, warehouse: 'B', qty: 0},
{_id: 12, productId: 2, warehouse: 'C', qty: 0}
]
I read about $lookup, $unwind, $project, and tried something like below but no where near to what I want:
Warehouse.aggregate([
{
$lookup:
{
from: "stocks",
pipeline: [
{ $project: { _id: 0, instock: {qty: 1, warehouse: 1} }},
{ $replaceRoot: { newRoot: { newStock : '$instock' } } }
],
as: "instock"
}
} ,
]);
hi, Anothony Winzlet, your advise works partially, for example:
{_id: 12, productId: 2, instock: [{warehouse: 'A', qty: 30}]
From your solution:
Result show only for warehouse A:
[{_id: 12, productId: 2, warehouse: 'A', qty: 30}]
Can I get for warehouse B & C as well? (will default qty to 0 if not defined)
[{_id: 12, productId: 2, warehouse: 'A', qty: 30},
{_id: 12, productId: 2, warehouse: 'B', qty: 0},
{_id: 12, productId: 2, warehouse: 'C', qty: 0}]
Not sure if above is possible to achieve ... thank you
Solution from Anthony Winzlet:
Warehouse.aggregate([
{ "$unwind": "$instock" },
{ "$replaceRoot": { "newRoot": { "$mergeObjects": ["$$ROOT", "$instock"] } }},
{ "$project": { "instock": 0 } }
])

Order Mongoose/MongoDB query results by number of values not in an array

I have two collections with the following simplified schemas:
// Ingredient
{
_id: Number
}
// Recipe
{
_id: Number,
ingredients: [{
type: Number,
ref: 'Ingredient'
}]
}
I'm trying to figure out how to implement a search query for recipes based on what ingredients you have available, sorted by the number of ingredients missing from each recipe.
For example, if I have the following data:
// Ingredients
{
_id: 1
},
{
_id: 2
},
{
_id: 3
},
{
_id: 4
},
{
_id: 5
}
// Recipes
{
_id: 1,
ingredients: [1, 2, 5]
},
{
_id: 2,
ingredients: [2, 4]
},
{
_id: 3,
ingredients: [2, 3]
}
and I input ingredients 2 and 3, the expected results would be
{
_id: 3,
ingredients: [2, 3] // Missing 0 ingredients
},
{
_id: 2,
ingredients: [2, 4] // Missing 1 ingredient
},
{
_id: 1,
ingredients: [1, 2, 5] // Missing 2 ingredients
}
Is it possible to do this with a query alone?
You can do this using $setDifference to find the missing ingredients, and then $size to get their count that you can then $sort on.
var ingredients = [2, 3];
db.recipes.aggregate([
{$project: {missing: {$setDifference: ['$ingredients', ingredients]}}},
{$project: {missing: 1, numMissing: {$size: '$missing'}}},
{$sort: {numMissing: 1}}
])
Results:
{ "_id" : 3, "missing" : [ ], "numMissing" : 0 }
{ "_id" : 2, "missing" : [ 4 ], "numMissing" : 1 }
{ "_id" : 1, "missing" : [ 1, 5 ], "numMissing" : 2 }

Search Exact Array Values In Multiple Fields

I have a collection which has 3 documents like below:
Collection:
{
name: "A",
arr: [1, 2, 3],
arr1: [4, 5, 6]
},
{
name: "B",
arr: [3, 7, 11],
arr1: [5, 6, 9]
},
{
name: "C",
arr: [3, 4, 5],
arr1: [7, 9, 12]
}
I want to search array below in the collection.
But all array values must be matched in fields "arr" or "arr1".
I mean array values can be in either fields but all values must be in the document.
So when I search array in the collection only second which has name:"B" and third which has name:"C" documents should be the result.
Because in the second document; first array value( 3 ) in the "arr" field and second and third array values(5 and 9) in the "arr1" field. In the third document first and second (3, 5) array values in the "arr" field and third array value (9) in the "arr1" field.
Array : [3, 5, 9]
Can you help me?
The best way to do this is using the $redact operator.
db.collection.aggregate([
{ "$redact": {
"$cond": [
{ "$setIsSubset": [ [3,5,9], { "$setUnion": [ "$arr", "$arr1" ] } ] },
"$$KEEP",
"$$PRUNE"
]}
}
])
You can also use $project with the $setUnion operator
and $match.
db.collection.aggregate([
{ "$project": { "name": 1, "arr": 1, "arr1": 1, "allvalues": { "$setUnion": [ "$arr", "$arr1" ]}}},
{ "$match": { "allvalues": { "$all": [3, 5, 9] }}}
])
Output:
{ "_id" : ObjectId("55d48fd2939d0f7d372d6dbe"), "name" : "B", "arr" : [ 3, 7, 11 ], "arr1" : [ 5, 6, 9 ] }
{ "_id" : ObjectId("55d48fd2939d0f7d372d6dbf"), "name" : "C", "arr" : [ 3, 4, 5 ], "arr1" : [ 7, 9, 12 ] }