How to retrieve elements with join query in MongoDB? - mongodb

I have a problem with sum detailed join with $graphLookUp and $map but the items retrieves double array :S, there is a way to group the elements in a sum?
This is an example of my collection:
/* 1 */
{
"type" : "Fase1",
"total" : 100,
"key" : "A6E19D93-8546-4A3A-8887-E23C301A290F",
},
/* 2 */
{
"type" : "Fase1",
"total" : 340.80,
"key": "85547A5B-623B-4408-9B3E-26F9B368B2C7"
},
/* 3 */
{
"type" : "Fase2",
"key": "F6307773-A6EF-41D4-A2F3-0FE55543846E"
"complement" : {
"payments" : [
{
"amount" : 20,
"documentsRelated" : [
{
"keyId" : "85547A5B-623B-4408-9B3E-26F9B368B2C7",
}
]
},
{
"amount" : 15,
"documentsRelated" : [
{
"keyId" : "85547A5B-623B-4408-9B3E-26F9B368B2C7",
}
]
}
],
},
}
I need to get the information in a list with the sum of type Phase2
/* 1 */
{
"type" : "Fase1",
"total" : 100,
"key" : "A6E19D93-8546-4A3A-8887-E23C301A290F",
},
/* 2 */
{
"type" : "Fase1",
"total" : 340.80,
"key": "85547A5B-623B-4408-9B3E-26F9B368B2C7",
//sum 20+35 from keyId related
"totalFase2": 35
}
there is my example code, I'm confused sum data in 1 column:
query = [
{
$match: {
type: 'Fase1',
},
}, {
$graphLookup: {
from: 'invoices',
startWith: '$key',
connectFromField: 'key',
connectToField: 'complement.payments.documentsRelated.keyId',
as: 'payments',
restrictSearchWithMatch: {
'type': 'Fase2',
},
},
},
{
$project: {
_id: 1,
type: 1,
total: 1,
paymentData: '$payments.complement.payments'
},
},
];
db.getCollection('invoices').aggregate(query);

Try $map to iterate loop of payments.complement.payments.amount array of array of amount, $sum array of amount, $map will return an array of number again $sum array of amount,
{
$project: {
_id: 1,
type: 1,
total: 1,
totalFase2: {
$sum: {
$map: {
input: "$payments.complement.payments.amount",
in: { $sum: "$$this" }
}
}
}
}
}
Playground

Related

MongoDB needs to get total counts from all objects

my aggregate query looks like this. I need to get the total count from the value.
db.getCollection('mydesk').aggregate([
{
$match: {
"accountId": ObjectId("616ea615edc5fa4278ccb7f6"),
"val" : { $ne : null},
"deskId": { "$in": [
ObjectId("61934f7efdb9dc5a7c1c3a01"),
ObjectId("61713730857c3243ec1d257c"),
ObjectId("629d9548e0c93e34e435e7b9"),
ObjectId("616eaf613bcd9655b8035a25"),
]}
}
},
{
$project: {
item: 1,
value: { $size: "$val.shapes" },
}
}
])
I got result like this. But need to get the total counts of my value.
/* 1 */
{
"_id" : ObjectId("616fab4f12b90d59d03f380e"),
"value" : 11
}
/* 2 */
{
"_id" : ObjectId("616fbad35700980a041cd190"),
"value" : 4
}
/* 3 */
{
"_id" : ObjectId("61713752857c3243ec1d257e"),
"value" : 12
}
Needed result :
{
"totalValueCount" : 27
}
Thanks in advance
One option is to use $group to $sum up the values:
db.getCollection('mydesk').aggregate([
{
$match: {
"accountId": ObjectId("616ea615edc5fa4278ccb7f6"),
"val" : { $ne : null},
"deskId": { "$in": [
ObjectId("61934f7efdb9dc5a7c1c3a01"),
ObjectId("61713730857c3243ec1d257c"),
ObjectId("629d9548e0c93e34e435e7b9"),
ObjectId("616eaf613bcd9655b8035a25"),
]}
}
},
{
$group: {
_id: null,
total: {$sum: { $size: "$val.shapes"}},
}
},
{$project: {_id: 0, total: 1}}
])

MongoDB sum of fields inside objects inside an array that is inside of an object greater than x

//8. isbn numbers of books that sold at least X copies (you decide the value for X).
Book example
{
isbn: "0001",
title: "Book1",
pages: NumberInt("150"),
price: NumberDecimal("321.2"),
copies: NumberInt("3"),
language: "english",
author: ["Author1"],
category: ["Space Opera"],
genre: ["Genre-1", "Genre-2"],
character: ["Character-1", "Character-2"],
},
Order example
{
orderNo: "3",
customerNo: "0003",
date: {
day: NumberInt("25"),
month: NumberInt("02"),
year: NumberInt("2021"),
},
orderLine: [
{
isbn: "0006",
price: NumberDecimal("341.0"),
amount: NumberInt("2"),
},
{
isbn: "0007",
price: NumberDecimal("170.5"),
amount: NumberInt("1"),
},
],
},
My try
I believe I have a mistake inside the pipeline at the group stage. For now I need at least to have isbn along with the copies sold in one object.
db.books.aggregate([ // editing this
{ $match : {} },
{
$lookup :
{
from : "orders",
pipeline : [
{
$group :
{
_id: null,
amount_total : { $sum : "$orderLine.amount" }
}
},
{ $project : { _id : 0, amount_total : 1} }
],
as : "amount"
}
},
{ $project : { _id : 0, isbn : 1, amount : 1} }
])
No idea why all are 0's, I was expecting at least some different numbers.
{
"isbn": "0001",
"amount": [
{
"amount_total": 0
}
]
},
{
"isbn": "0002",
"amount": [
{
"amount_total": 0
}
]
},
{
"isbn": "0003",
"amount": [
{
"amount_total": 0
}
]
},// and so on
Apparently, this does what I wanted.
db.books.aggregate([
{
$lookup: {
from: "orders",
let: { isbn: "$isbn" }, // Pass this variable to pipeline for Joining condition.
pipeline: [
{ $unwind: "$orderLine" },
{
$match: {
// Join condition.
$expr: { $eq: ["$orderLine.isbn", "$$isbn"] }
}
},
{
$project: { _id: 0 , orderNo : 1, "orderLine.amount": 1}
}
],
as: "amount"
}
}, { $project : { _id : 0, isbn : 1, amount_total : { $sum : "$amount.orderLine.amount" } } }
])
In your query $lookup is performing a join operation without any condition instead try this query:
db.books.aggregate([
{
$lookup: {
from: "orders",
let: { isbn: "$isbn" },
pipeline: [
{ $unwind: "$orderLine" },
{
$match: {
$expr: { $eq: ["$orderLine.isbn", "$$isbn"] }
}
}
],
as: "amount"
}
},
{
$project: {
_id: 0,
isbn: 1,
amount_total: { $sum: "$amount.orderLine.amount" }
}
}
]);
Test data:
books collection:
/* 1 createdAt:3/12/2021, 10:41:13 AM*/
{
"_id" : ObjectId("604af7f14b5860176c2254b7"),
"isbn" : "0001",
"title" : "Book1"
},
/* 2 createdAt:3/12/2021, 10:41:13 AM*/
{
"_id" : ObjectId("604af7f14b5860176c2254b8"),
"isbn" : "0002",
"title" : "Book2"
}
orders collection:
/* 1 createdAt:3/12/2021, 11:10:51 AM*/
{
"_id" : ObjectId("604afee34b5860176c2254ce"),
"orderNo" : "1",
"customerNo" : "0001",
"orderLine" : [
{
"isbn" : "0001",
"price" : 341,
"amount" : 2
},
{
"isbn" : "0002",
"price" : 170.5,
"amount" : 1
},
{
"isbn" : "0003",
"price" : 190.5,
"amount" : 3
}
]
},
/* 2 createdAt:3/12/2021, 11:10:51 AM*/
{
"_id" : ObjectId("604afee34b5860176c2254cf"),
"orderNo" : "3",
"customerNo" : "0003",
"orderLine" : [
{
"isbn" : "0001",
"price" : 341,
"amount" : 2
},
{
"isbn" : "0002",
"price" : 170.5,
"amount" : 1
},
{
"isbn" : "0003",
"price" : 190.5,
"amount" : 3
}
]
}
Output:
/* 1 */
{
"isbn" : "0001",
"amount_total" : 4
},
/* 2 */
{
"isbn" : "0002",
"amount_total" : 2
}
The $sum inside $group stage will sum root and grouped fields but here orderLine field is an array, you need to sum that array of numbers before applying $sum, it means nested $sum operation,
{
$group: {
_id: null,
amount_total: {
$sum: {
$sum: "$orderLine.amount"
}
}
}
}
Playground
Try the final solution,
$match isbn array in orderLine.isbn using $in condition
$filter to iterate look of orderLine array, and match isbn, it will return filtered documents
$let declare a orders variable to hold above filtered documents of orderLine, sum the amount from filtered array using $sum
$project to show required fields, and get total sum of amount_total array
db.books.aggregate([
{
$lookup: {
from: "orders",
let: { isbn: "$isbn" },
pipeline: [
{ $match: { $expr: { $in: ["$$isbn", "$orderLine.isbn"] } } },
{
$project: {
_id: 0,
amount_total: {
$let: {
vars: {
orders: {
$filter: {
input: "$orderLine",
cond: { $eq: ["$$this.isbn", "$$isbn"] }
}
}
},
in: { $sum: "$$orders.amount" }
}
}
}
}
],
as: "amount"
}
},
{
$project: {
_id: 0,
isbn: 1,
amount_total: { $sum: "$amount.amount_total" }
}
}
])
Playground

$divide elements of Embedded Documents - MongoDB Aggregation

I am trying to create an aggregation MongoDB query.
Structure of data:
{
"object_name": Example,
"values": [ {"name":"value1", "value":1},
{"name":"value2", "value":10},
{"name":"total", "value":105}
}
Goal: Find object names where value1/total > 0.5 and value2/total > 0.25 and total > 100.
The data is structured in this way to provide indexes on the value_name and value fields.
What I tried - aggregate with the following pipelines:
$match: filter documents with total > 100:
$match: { values: { $elemMatch: { value_name: "total", value: {$gte: 100 }
$project: grab only the value_names that we need (there are close to 200 different names)
$project: {
values: {
$filter: {
input: "$values",
as: "value",
cond: { $or: [
{ $eq: [ "$$value.name", "name1"] },
{ $eq: [ "$$value.name", "name2"] },
{ $eq: [ "$$value.name", "total"] },
] }
}
},
name: 1
}
then, { $unwind: "$values" }
And here, I could $group to $divide: name1/total, name2/total however I'm stuck on how to get those values.
I can't simply do stats.value: because it does not know which value I'm referring to. I believe $group can't do $elemMatch to also match the name.
If there are simpler solutions that this, I'd greatly appreciate your input.
Please try this :
We're filtering documents where values array has an object with
name : total & value > 100.
Adding object with name : total
to document.
Leaving only objects that match with criteria
value1/total > 0.5 and value2/total > 0.25 in values array.
If
size of that array is greater than 1, then those two conditions are
met.
Finally projecting only object_name
Query :
db.yourCollectionName.aggregate([{ $match: { values: { $elemMatch: { name: "total", value: { $gte: 100 } } } } },
{
$addFields: {
totalValue: {
$arrayElemAt: [{
$filter: {
input: "$values",
as: "item",
cond: { $eq: ["$$item.name", 'total'] }
}
}, 0]
}
}
},
{
$project: {
values: {
$filter: {
input: "$values",
as: "value",
cond: {
$or: [
{ $cond: [{ $eq: ["$$value.name", "value1"] }, { $gt: [{ $divide: ["$$value.value", '$totalValue.value'] }, 0.5] }, false] },
{ $cond: [{ $eq: ["$$value.name", "value2"] }, { $gt: [{ $divide: ["$$value.value", '$totalValue.value'] }, 0.25] }, false] }
]
}
}
}, object_name: 1
}
}, {
$match: {
$expr: { $gt: [{ $size: "$values" }, 1] }
}
}, { $project: { object_name: 1, _id: 0 } }])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e20bd94d02e05b694d55fa5"),
"object_name" : "Example",
"values" : [
{
"name" : "value1",
"value" : 1
},
{
"name" : "value2",
"value" : 10
},
{
"name" : "total",
"value" : 105
},
{
"name" : "total1",
"value" : 105
}
]
}
/* 2 */
{
"_id" : ObjectId("5e20bdb1d02e05b694d56490"),
"object_name" : "Example2",
"values" : [
{
"name" : "value1",
"value" : 1
},
{
"name" : "value2",
"value" : 10
},
{
"name" : "total",
"value" : 5
},
{
"name" : "total1",
"value" : 5
}
]
}
/* 3 */
{
"_id" : ObjectId("5e20d1b7d02e05b694d7c57a"),
"object_name" : "Example3",
"values" : [
{
"name" : "value1",
"value" : 100
},
{
"name" : "value2",
"value" : 100
},
{
"name" : "total",
"value" : 200
},
{
"name" : "total1",
"value" : 205
}
]
}
/* 4 */
{
"_id" : ObjectId("5e20d1cad02e05b694d7c71c"),
"object_name" : "Example4",
"values" : [
{
"name" : "value1",
"value" : 200
},
{
"name" : "value2",
"value" : 40
},
{
"name" : "total",
"value" : 200
},
{
"name" : "total1",
"value" : 205
}
]
}
/* 5 */
{
"_id" : ObjectId("5e20d1e2d02e05b694d7c933"),
"object_name" : "Example5",
"values" : [
{
"name" : "value1",
"value" : 150
},
{
"name" : "value2",
"value" : 100
},
{
"name" : "total",
"value" : 200
},
{
"name" : "total1",
"value" : 205
}
]
}
Result :
/* 1 */
{
"object_name" : "Example5"
}
You may convert your array into object with $arrayToObject operator and add tmp field to have easy access to value1, value2, total values
db.collection.aggregate([
{
$addFields: {
tmp: {
$arrayToObject: {
$map: {
input: "$values",
as: "value",
in: {
k: "$$value.name",
v: "$$value.value"
}
}
}
},
name: 1
}
},
{
$match: {
$expr: {
$and: [
{
$gt: [
{
$divide: [
"$tmp.value1",
"$tmp.total"
]
},
0.5
]
},
{
$gt: [
{
$divide: [
"$tmp.value2",
"$tmp.total"
]
},
0.25
]
},
{
$gt: [
"$tmp.total",
100
]
}
]
}
}
},
{
$project: {
tmp: 0
}
}
])
MongoPlayground

Mongodb Group by get $max and count of max in value and percent of that group

I need to a group by on x field and get the max value of other fields. Yes, using $max we can get max repetitive value. But, I also need to get count $max value in percent/count too. In other words, how many times this $max value exist in that group. Kindly help.
example:
db.getCollection("test").aggregate(
[
{ "$match" : { "doc_id" : 1.0 } },
{ "$group" : {
"_id" : { "name" : "$name" },
"total" : { "$sum" : "$amount" },
"l1_max" : { "$max" : "$l1" }
}
},
]
);
Here, I am getting l1_max = 'Computer' . But, I need it as 'Computer - (30%) Total 4/12'
Updated: 20/10/2019
#mickl : Thanks for the answer.
The field l1 is actually a referenced field. In normal find/project or mongoose populate(), it helps to get fields from other collection. Example:
if l1 is of type ObjectId then,
l1: {
_id, "4343434343sdsdsY",
name: "IT"
}
So l1.name will fetch name field from another collection in project/populate function.
I executed following code:
db.getCollection("test").aggregate(
[
{ "$match" : { "doc_id" : 1.0 } },
{ "$group" : {
"_id" : { "name" : "$name" },
"total" : { "$sum" : "$amount" },
"count": { '$sum': 1 },
"l1_max" : { "$max" : "$l1" },
"l1_values": { $push: "$l1" }
}
},
{
$project: {
_id: 1,
total: 1,
l1 : {"_id": "$l1_max", "count": "$count", "percent": { $divide: [ { $size: { $filter: { input: "$l1_values", cond: { $eq: [ "$$this", "$l1_max" ] } } } },"$count"]}}
}
}
]
);
Answer is like below: But I also need referenced name field too.
{
"_id" : {
"name" : "xzy"
},
"total" : 35.0,
"l1" : {
"_id" : "4343920239201W",
"name" : "IT", // **MISSING**
"count" : 4.0,
"percent" : 0.25
}
}
Hope I was clear this time.
You need to capture all l1 values in your group and the calculate the percent using $divide, $filter and $size:
db.getCollection("test").aggregate(
[
{ "$match" : { "doc_id" : 1.0 } },
{ "$group" : {
"_id" : { "name" : "$name" },
"total" : { "$sum" : "$amount" },
"l1_max" : { "$max" : "$l1" },
"l1_values": { $push: "$l1" }
}
},
{
$project: {
_id: 1,
total: 1,
l1_max: 1,
l1_perc: {
$divide: [
{ $size: { $filter: { input: "$l1_values", cond: { $eq: [ "$$this", "$l1_max" ] } } } },
{ $size: "$l1_values" }
]
}
}
}
]
);
Mongo Playground

I need help in this query mongodb with nodejs

The query is returning double value, can you help me with this?
the problem stay in unwind ?
is a consultation that consolidates the debits and credits of the db...
with aggregate...
Code:
My DAO ( MongoDB + NodeJS ) :
ListarDebitosDAO.prototype.consolidado = function(usuario,res,req){
this._connection.open(function(err, mongoclient){
mongoclient.collection("contas", function(err, collection){
collection.aggregate([{
$match : {usuario : usuario}},{
$unwind: "$debitos"
},{
$unwind: "$creditos"
}, {
$group: {
"_id" : usuario,
"debitos": {
$sum: "$debitos.debito"
},
"creditos" : {
$sum: "$creditos.credito"
}
}
}])
.toArray(function(error, results){
console.log(results)
var debitoConsolidado = results[0].debitos;
var creditoConsolidado = results[0].creditos;
res.render('dashboard', {debitoConsolidado,nome_usuario: req.session.nome,creditoConsolidado})
});
// mongoclient.close();
});
});
}
My collection ( exemple ) :
{
"_id" : ObjectId("5a09113b42e54fb49230fa37"),
"usuario" : "sara",
"creditos" : [
{
"nome_do_credito" : "credito inicial",
"credito" : 0
},
{
"nome_do_credito" : "salario",
"credito" : 1200
}
],
"debitos" : [
{
"nome_do_debito" : "debito inicial",
"debito" : 0
},
{
"nome_do_debito" : "Vivo",
"debito" : 200
},
{
"nome_do_debito" : "Vivo",
"debito" : 600
}
]
}
My atual output:
{ "_id" : null, "debitos" : 1600, "creditos" : 3600 }
I need :
{ "_id" : null, "debitos" : 800, "creditos" : 1200 }
enter code here
The problem is that you are trying to use both unwind simultaneously, it is creating duplicate entries hence the sum of credit and debit is coming wrong.
I guess what you can do is do sequential unwind and grouping for both credit and debit
collection.aggregate([{
$match : {usuario : usuario}},{
$unwind: "$debitos"
}, {
$group: {
"_id" : usuario,
"debitos": {
$sum: "$debitos.debito"
},
"creditos" : "$creditos"
}
},
{
$unwind: "$creditos"
},
{
$group: {
"_id" : usuario,
"creditos" : {
$sum: "$creditos.credito"
}
"debitos": "$debitos"
}
}
])
I am sure either this or some variant should work for you.
It can be as simple as this:
db.contas.aggregate([
{
$project: {
_id: 1,
usario: 1,
debitos: {
$reduce: {
input: "$debitos",
initialValue: 0,
in: { $add: [ "$$value", "$$this.debito" ] }
}
},
creditos: {
$reduce: {
input: "$creditos",
initialValue: 0,
in: { $add: [ "$$value", "$$this.credito" ] }
}
}
}
}
]);
You can use just one projection and reduce the arrays by summing their members.