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

//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

Related

How to retrieve elements with join query in 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

$unwind, $aggregation manipulation in mongodb nodejs

please check this query
db.billsummaryofthedays.aggregate([
{
'$match': {
'userId': ObjectId('5e43de778b57693cd46859eb'),
'adminId': ObjectId('5e43e5cdc11f750864f46820'),
'date': ISODate("2020-02-11T16:30:00Z"),
}
},
{
$lookup:
{
from: "paymentreceivables",
let: { userId: '$userId', adminId: '$adminId' },
pipeline: [
{
$match:
{
paymentReceivedOnDate:ISODate("2020-02-11T16:30:00Z"),
$expr:
{
$and:
[
{ $eq: ["$userId", "$$userId"] },
{ $eq: ["$adminId", "$$adminId"] }
]
}
}
},
{ $project: { amount: 1, _id: 0 } }
],
as: "totalPayment"
}
}, {'$unwind':'$totalPayment'},
{ $group:
{ _id:
{ date: '$date',
userId: '$userId',
adminId: '$adminId' },
totalBill:
{
$sum: '$billOfTheDay'
},
totalPayment:
{
$sum: '$totalPayment.amount'
}
}
},
}
}])
this is the result i am getting in the shell
{
"_id" : {
"date" : ISODate("2020-02-11T18:30:00Z"),
"userId" : ObjectId("5e43de778b57693cd46859eb"),
"adminId" : ObjectId("5e43e5cdc11f750864f46820")
},
"totalBill" : 1595.6799999999998,
"totalPayments" : 100
}
now this is not what i expected,i assume due to {'$unwind':'$totalPayment'} it takes out all the values from the array and because of which every document is getting counted 2 times. When i remove {'$unwind':'$totalPayment'} then totalBill sum turns out to be correct but totalPayment is 0.
I have tried several other ways but not able to achieve the desired result
Below are my collections:
// collection:billsummaryofthedays//
{
"_id" : ObjectId("5e54f784f4032c1694535c0e"),
"userId" : ObjectId("5e43de778b57693cd46859eb"),
"adminId" : ObjectId("5e43e5cdc11f750864f46820"),
"date" : ISODate("2020-02-11T16:30:00Z"),
"UID":"acex01"
"billOfTheDay" : 468,
}
{
"_id" : ObjectId("5e54f784f4032c1694535c0f"),
"UID":"bdex02"
"userId" : ObjectId("5e43de778b57693cd46859eb"),
"adminId" : ObjectId("5e43e5cdc11f750864f46820"),
"date" : ISODate("2020-02-11T16:30:00Z"),
"billOfTheDay" : 329.84,
}
// collection:paymentreceivables//
{
"_id" : ObjectId("5e43e73169fe1e3fc07eb7c5"),
"paymentReceivedOnDate" : ISODate("2020-02-11T16:30:00Z"),
"adminId" : ObjectId("5e43e5cdc11f750864f46820"),
"userId" : ObjectId("5e43de778b57693cd46859eb"),
"amount" : 20,
}
{
"_id" : ObjectId("5e43e73b69fe1e3fc07eb7c6"),
"paymentReceivedOnDate" : ISODate("2020-02-11T16:30:00Z"),
"adminId" : ObjectId("5e43e5cdc11f750864f46820"),
"userId" : ObjectId("5e43de778b57693cd46859eb"),
"amount" : 30,
}
desired result should be totalBill:797.83 i.e[468+329.84,] and totalPayment:50 i.e[30+20,] but i am getting double the expected result and even if i am able to calculate one of the value correctly the other one result 0.How to tackle this??
Since you've multiple documents with same data in billsummaryofthedays collection then you can group first & then do $lookup - that way JOIN between two collections would be 1-Vs-many rather than many-Vs-many as like it's currently written, So you can try below query for desired o/p & performance gains :
db.billsummaryofthedays.aggregate([
{
"$match": {
"userId": ObjectId("5e43de778b57693cd46859eb"),
"adminId": ObjectId("5e43e5cdc11f750864f46820"),
"date": ISODate("2020-02-11T16:30:00Z"),
}
},
{
$group: {
_id: {
date: "$date",
userId: "$userId",
adminId: "$adminId"
},
totalBill: {
$sum: "$billOfTheDay"
}
}
},
{
$lookup: {
from: "paymentreceivables",
let: {
userId: "$_id.userId",
adminId: "$_id.adminId"
},
pipeline: [
{
$match: {
paymentReceivedOnDate: ISODate("2020-02-11T16:30:00Z"),
$expr: {
$and: [
{
$eq: [
"$userId",
"$$userId"
]
},
{
$eq: [
"$adminId",
"$$adminId"
]
}
]
}
}
},
{
$project: {
amount: 1,
_id: 0
}
}
],
as: "totalPayment"
}
},
{
$addFields: {
totalPayment: {
$reduce: {
input: "$totalPayment",
initialValue: 0,
in: {
$add: [
"$$value",
"$$this.amount"
]
}
}
}
}
}
])
Test : MongoDB-Playground

How can i count total documents and also grouped counts simultanously in mongodb aggregation?

I have a dataset in mongodb collection named visitorsSession like
{ip : 192.2.1.1,country : 'US', type : 'Visitors',date : '2019-12-15T00:00:00.359Z'},
{ip : 192.3.1.8,country : 'UK', type : 'Visitors',date : '2019-12-15T00:00:00.359Z'},
{ip : 192.5.1.4,country : 'UK', type : 'Visitors',date : '2019-12-15T00:00:00.359Z'},
{ip : 192.8.1.7,country : 'US', type : 'Visitors',date : '2019-12-15T00:00:00.359Z'},
{ip : 192.1.1.3,country : 'US', type : 'Visitors',date : '2019-12-15T00:00:00.359Z'}
I am using this mongodb aggregation
[{$match: {
nsp : "/hrm.sbtjapan.com",
creationDate : {
$gte: "2019-12-15T00:00:00.359Z",
$lte: "2019-12-20T23:00:00.359Z"
},
type : "Visitors"
}}, {$group: {
_id : "$country",
totalSessions : {
$sum: 1
}
}}, {$project: {
_id : 0,
country : "$_id",
totalSessions : 1
}}, {$sort: {
country: -1
}}]
using above aggregation i am getting results like this
[{country : 'US',totalSessions : 3},{country : 'UK',totalSessions : 2}]
But i also total visitors also along with result like totalVisitors : 5
How can i do this in mongodb aggregation ?
You can use $facet aggregation stage to calculate total visitors as well as visitors by country in a single pass:
db.visitorsSession.aggregate( [
{
$match: {
nsp : "/hrm.sbtjapan.com",
creationDate : {
$gte: "2019-12-15T00:00:00.359Z",
$lte: "2019-12-20T23:00:00.359Z"
},
type : "Visitors"
}
},
{
$facet: {
totalVisitors: [
{
$count: "count"
}
],
countrySessions: [
{
$group: {
_id : "$country",
sessions : { $sum: 1 }
}
},
{
$project: {
country: "$_id",
_id: 0,
sessions: 1
}
}
],
}
},
{
$addFields: {
totalVisitors: { $arrayElemAt: [ "$totalVisitors.count" , 0 ] },
}
}
] )
The output:
{
"totalVisitors" : 5,
"countrySessions" : [
{
"sessions" : 2,
"country" : "UK"
},
{
"sessions" : 3,
"country" : "US"
}
]
}
You could be better off with two queries to do this.
To save the two db round trips following aggregation can be used which IMO is kinda verbose (and might be little expensive if documents are very large) to just count the documents.
Idea: Is to have a $group at the top to count documents and preserve the original documents using $push and $$ROOT. And then before other matches/filter ops $unwind the created array of original docs.
db.collection.aggregate([
{
$group: {
_id: null,
docsCount: {
$sum: 1
},
originals: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$originals"
},
{ $match: "..." }, //and other stages on `originals` which contains the source documents
{
$group: {
_id: "$originals.country",
totalSessions: {
$sum: 1
},
totalVisitors: {
$first: "$docsCount"
}
}
}
]);
Sample O/P: Playground Link
[
{
"_id": "UK",
"totalSessions": 2,
"totalVisitors": 5
},
{
"_id": "US",
"totalSessions": 3,
"totalVisitors": 5
}
]

How to check $setDifference in two array using mongo-query

UserDetails
{
"_id" : "5c23536f807caa1bec00e79b",
"UID" : "1",
"name" : "A",
},
{
"_id" : "5c23536f807caa1bec00e78b",
"UID" : "2",
"name" : "B",
}
UserProducts
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "100",
"UID" : "1"
},
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "200",
"UID" : "2"
}
Groups
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"200" // UPID
],
}
}
Step 1
I have to take UID from UserDetails check with UserProducts then take UPID from UserProducts
Step 2
we have to check this UPID mapped to Groups collection or not ?.
members.regularStudent we are mapped UPID
Step 3
Suppose UPID not mapped means i want to print the UPID from from UserProducts
I have tried but couldn't complete this, kindly help me out on this.
Expected Output:
["100"]
Note: Expected Output is ["100"] , because UserProducts having UPID 100 & 200 but Groups collection mapped only 200.
My Code
db.UserDetails.aggregate(
{
$lookup: {
from: "UserProducts",
localField: "UID",
foreignField: "UID",
as: "userProduct"
}
},
{ $unwind: "$userProduct" },
{
"$project": { "_id" : 0, "userProduct.UPID" : 1 }
},
{
$group: {
_id: null,
userProductUPIDs: { $addToSet: "$userProduct.UPID" }
}
}
) // returns [ "100", "200" ]
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
}
]) // returns ["200"]
Now i want to check $setDifference of both array, so i had added below code but returning error like $userProductUPIDs is not defined
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
},
{
$project: {
members: {
$setDifference: [ $userProductUPIDs , "$members" ]
},
_id : 0
}
}
])
As this is a follow up to one of my previous answers I will try to fix your code. The bottom line is that you need two queries as you can't upgrade your database so the code should look like below:
var queryResult = db.UserDetails.aggregate(
{
$lookup: {
from: "UserProducts",
localField: "UID",
foreignField: "UID",
as: "userProduct"
}
},
{ $unwind: "$userProduct" },
{
"$project": { "_id" : 0, "userProduct.UPID" : 1 }
},
{
$group: {
_id: null,
userProductUPIDs: { $addToSet: "$userProduct.UPID" }
}
});
let userProductUPIDs = queryResult.toArray()[0].userProductUPIDs;
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
},
{
$project: {
members: {
$setDifference: [ userProductUPIDs , "$UPIDs" ]
},
_id : 0
}
}
]) // should return 100

using mongo aggregation how to replace the fields names [duplicate]

I have large collection of documents which represent some kind of events. Collection contains events for different userId.
{
"_id" : ObjectId("57fd7d00e4b011cafdb90d22"),
"userId" : "123123123",
"userType" : "mobile",
"event_type" : "clicked_ok",
"country" : "US",
"timestamp" : ISODate("2016-10-12T00:00:00.308Z")
}
{
"_id" : ObjectId("57fd7d00e4b011cafdb90d22"),
"userId" : "123123123",
"userType" : "mobile",
"event_type" : "clicked_cancel",
"country" : "US",
"timestamp" : ISODate("2016-10-12T00:00:00.308Z")
}
At midnight I need to run aggregation for all documents for the previous day. Documents need to aggregated in the way so I could get number of different events for particular userId.
{
"userId" : "123123123",
"userType" : "mobile",
"country" : "US",
"clicked_ok" : 23,
"send_message" : 14,
"clicked_cancel" : 100,
"date" : "2016-11-24",
}
During aggregation I need to perform two things:
calculate number of events for particular userId
add "date" text fields with date
Any help is greatly appreciated! :)
you can do this with aggregation like this :
db.user.aggregate([
{
$match:{
$and:[
{
timestamp:{
$gte: ISODate("2016-10-12T00:00:00.000Z")
}
},
{
timestamp:{
$lt: ISODate("2016-10-13T00:00:00.000Z")
}
}
]
}
},
{
$group:{
_id:"$userId",
timestamp:{
$first:"$timestamp"
},
send_message:{
$sum:{
$cond:[
{
$eq:[
"$event_type",
"send_message"
]
},
1,
0
]
}
},
clicked_cancel:{
$sum:{
$cond:[
{
$eq:[
"$event_type",
"clicked_cancel"
]
},
1,
0
]
}
},
clicked_ok:{
$sum:{
$cond:[
{
$eq:[
"$event_type",
"clicked_ok"
]
},
1,
0
]
}
}
}
},
{
$project:{
date:{
$dateToString:{
format:"%Y-%m-%d",
date:"$timestamp"
}
},
userId:1,
clicked_cancel:1,
send_message:1,
clicked_ok:1
}
}
])
explanation:
keep only document for a specific day in $match stage
group doc by userId and count occurrences for each event in $group stage
finally format the timestamp field into yyyy_MM-dd format in $project stage
for the data you provided, this will output
{
"_id":"123123123",
"send_message":0,
"clicked_cancel":1,
"clicked_ok":1,
"date":"2016-10-12"
}
Check the following query
db.sandbox.aggregate([{
$group: {
_id: {
userId: "$userId",
date: {
$dateToString: { format: "%Y-%m-%d", date: "$timestamp" }}
},
send_message: {
$sum: {
$cond: { if: { $eq: ["$event_type", "send_message"] }, then: 1, else: 0 } }
},
clicked_cancel: {
$sum: {
$cond: { if: { $eq: ["$event_type", "clicked_cancel"] }, then: 1, else: 0 }
}
},
clicked_ok: {
$sum: {
$cond: { if: { $eq: ["$event_type", "clicked_ok"] }, then: 1, else: 0 }
}
}
}
}])