Inserting/Updating Aggregated data on Mongo - mongodb

Consider the following collection:
requests->
{
_id : ObjectId("573f28f49b0ffc283676f736"),
date : '2018-12-31',
userId: ObjectId("5c1239e93a7b9a72ef1c9197"),
serviceId: ObjectId("572e29b0116b5db3057bd821"),
status: 'completed'
}
I have an aggregate operation on requests collection which returns documents in the following format:
{
"grossRequests" : 2,
"grossData" : [
{
"date" : "2018-08-04",
"count" : 1,
"requests" : [
ObjectId("5b658147c73beb5ea3debc6e")
]
},
{
"date" : "2018-08-05",
"count" : 1,
"requests" : [
ObjectId("5b658160572faa5dd033fb48")
]
}
],
"netData" : [
{
"date" : "2018-08-05",
"count" : 1,
"requests" : [
ObjectId("5b658160572faa5dd033fb48")
]
}
],
"netRequests" : 1.0,
"userId" : ObjectId("5c1239e93a7b9a72ef1c9197"),
"serviceId" : "572e29b0116b5db3057bd821"
}
Above is the document to be inserted in cumulativeData
Now, I need to add all the documents returned by my aggregation operation, into a collection called cumulativeData.
The cumulativeData collection has one document per userId per serviceType.
I am running a query for specific date ranges and while inserting them into the collection, would like to "merge" the documents, rather than replacing them.
so, for example, if I am looping through all the documents returned by the aggregation operation using forEach, for each new document I get ({userId,serviceType} pair which is not present in the cumulativeData collection) I need to insert it as is and for every document which is already present in the collection, I need to update it as follows.
grossRequests -> add both values
grossData -> push the new values into the existing set
netData -> push the new values into the existing set
netRequests -> add both values
The operation would be as follows,
Existing doc in ** cumulativeData **
{
"grossRequests": 2,
"grossData": [{
"date": "2018-08-04",
"count": 1,
"requests": [
ObjectId("5b658147c73beb5ea3debc6e")
]
}, {
"date": "2018-08-05",
"count": 1,
"requests": [
ObjectId("5b658160572faa5dd033fb48")
]
}
],
"netData": [{
"date": "2018-08-05",
"count": 1,
"requests": [
ObjectId("5b658160572faa5dd033fb48")
]
}
],
"netRequests": 1,
"userId": ObjectId("5c1239e93a7b9a72ef1c9197"),
"serviceId": "572e29b0116b5db3057bd821"
}
New Document generated after aggregation on a new date range
{
"grossRequests": 2,
"grossData": [{
"date": "2018-08-04",
"count": 1,
"requests": [
ObjectId("5b658147c73beb5ea3debc8e")
]
}, {
"date": "2018-08-05",
"count": 1,
"requests": [
ObjectId("5b658160572faa5dd033fb4l")
]
}
],
"netData": [{
"date": "2018-08-05",
"count": 1,
"requests": [
ObjectId("5b658160572faa5dd033fb4l")
]
}
],
"netRequests": 1,
"userId": ObjectId("5c1239e93a7b9a72ef1c9197"),
"serviceId": "572e29b0116b5db3057bd821"
}
Final Result
{
"grossRequests": 4,
"grossData": [{
"date": "2018-08-04",
"count": 2,
"requests": [
ObjectId("5b658147c73beb5ea3debc6e") , ObjectId("5b658147c73beb5ea3debc8e")
]
}, {
"date": "2018-08-05",
"count": 2,
"requests": [
ObjectId("5b658160572faa5dd033fb48"), ObjectId("5b658160572faa5dd033fb4l")
]
}
],
"netData": [{
"date": "2018-08-05",
"count": 2,
"requests": [
ObjectId("5b658160572faa5dd033fb48"),ObjectId("5b658160572faa5dd033fb4l")
]
}
],
"netRequests": 2,
"userId": ObjectId("5c1239e93a7b9a72ef1c9197"),
"serviceId": "572e29b0116b5db3057bd821"
}

You can use below code to perform updates.
It involves two steps first step when record with userid/service id not found insert data else update data.
Within update step first update the top fields i.e count followed by iterating netData and grossData to perform the merge.
To perform merge we use the writeresult which has nmodified count to identify if we have to update the array value or insert new array value.
You can adjust below query to meet your needs.
db.getCollection('requests').aggregate(your aggregation query).forEach(function(doc) {
var user_id = doc.userId;
var service_id = doc.serviceId;
var gross_requests = doc.grossRequests;
var net_requests = doc.netRequests;
var gross_data = doc.grossData;
var net_data = doc.netData;
var matched = db.getCollection('cumulativeData').findOne({
"userId": user_id,
serviceId: service_id
});
if (matched == null) {
db.getCollection('cumulativeData').insert(doc);
} else {
db.getCollection('cumulativeData').update({
"userId": user_id,
serviceId: service_id
}, {
$inc: {
"grossRequests": gross_requests,
"netRequests": net_requests
}
});
gross_data.forEach(function(grdoc) {
var writeresult = db.getCollection('cumulativeData').update({
"userId": user_id,
serviceId: service_id,
"grossData.date": grdoc.date
}, {
$inc: {
"grossData.$.count": grdoc.count
},
$push: {
"grossData.$.requests": {
$each: grdoc.requests
}
}
});
if (writeresult.nModified == 0) {
db.getCollection('cumulativeData').update({
"userId": user_id,
serviceId: service_id
}, {
$push: {
"grossData": {
"count": grdoc.count,
"requests": grdoc.requests,
"date": grdoc.date
}
}
});
}
});
net_data.forEach(function(nrdoc) {
var writeresult = db.getCollection('cumulativeData').update({
"userId": user_id,
serviceId: service_id,
"netData.date": nrdoc.date
}, {
$inc: {
"netData.$.count": nrdoc.count
},
$push: {
"netData.$.requests": {
$each: nrdoc.requests
}
}
});
if (writeresult.nModified == 0) {
db.getCollection('cumulativeData').update({
"userId": user_id,
serviceId: service_id
}, {
$push: {
"netData": {
"count": nrdoc.count,
"requests": nrdoc.requests,
"date": nrdoc.date
}
}
});
}
});
}
});

Related

Is it possible to aggregate $$ROOT in mongo db?

I have following Mongo Collection.
[
{
"query": "a",
"page": "p1",
"clicks": 10,
"date": "x"
},
{
"query": "b",
"page": "p1",
"clicks": 5,
"date": "x"
},
{
"query": "a",
"page": "p1",
"clicks": 5,
"date": "y"
},
{
"query": "c",
"page": "p2",
"clicks": 2,
"date": "y"
},
]
Output Should be like this :
[
{
"page" : "p1",
"most_clicks_query" : "a",
"sum_of_clicks_for_query" : 15
},
{
"page" : "p2",
"most_clicks_query" : "c",
"sum_of_clicks_for_query" : 2
},
]
Logic to get this Output :
I need the query name that has most clicks for each page with sum of clicks (for that query)
What I ask :
I am hoping to get this result in one aggregation query.
So I am playing with $$ROOT.
In this path, now I am stuck with grouping the $$ROOT (to get sum of clicks for queries).
Can someone guide me a better path to do this?
Here is the aggregation you're looking for:
db.collection.aggregate([
{
"$group": {
"_id": {
"page": "$page",
"query": "$query"
},
"sum_of_clicks_for_query": {
"$sum": "$clicks"
}
}
},
{
"$project": {
"_id": false,
"page": "$_id.page",
"most_clicks_query": "$_id.query",
"sum_of_clicks_for_query": true
}
},
{
$sort: {
"sum_of_clicks_for_query": -1
}
},
{
$group: {
_id: "$page",
group: {
$first: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: "$group"
}
}
])
Playground: https://mongoplayground.net/p/Uzk3CuSwVRM

get sum of integer from array of objects in mongodb

I want to filter my documents by sum of decimal field in array of objects, but didn't find anything good enough. for example I have documents like below:
[
{
"id": 1,
"limit": NumberDecimal("100000"),
"requests": [
{
"money": NumberDecimal("50000"),
"user": "user1"
}
]
},
{
"id": 2,
"limit": NumberDecimal("100000"),
"requests": [
{
"money": NumberDecimal("100000"),
"user": "user2"
}
]
},
{
"id": 1,
"limit": null,
"requests": [
{
"money": NumberDecimal("50000"),
"user": "user1"
},
{
"money": NumberDecimal("50000"),
"user": "user3"
}
]
},
]
description by documents fields:
limit - maximum amount of money, that I have
requests - array of objects, where money it's how much money user get from limit (if user1 get 50000 money there remainder it's 50000, limit - sum(requests.money))
I am making query in mongodb from scala projects:
get all documents where limit equal to null
get all documents where I have x remainder money (x like input value)
first case it's more easy than second one, I know how I can get sum of requests.money: I am doing it by this query:
db.campaign.aggregate([
{$project: {
total: {$sum: ["$requests.money"]}
}}
])
scala filter part
Filters.or(
Filters.equal("limit", null),
Filters.expr(Document(s""" {$$project: {total: {$$sum: ["$$requests.money"]}}}"""))
)
But I don't want to store it and get as result, I want to filter by this condition x (money which I want to get by some user) limit >= sum(requests.money) + x. And by this filter I want to get all filtered documents.
Example:
x = 50000
and output must be like this:
[
{
"id": 1,
"limit": NumberDecimal("100000"),
"requests": [
{
"money": NumberDecimal("50000"),
"user": "user1"
}
]
},
{
"id": 1,
"limit": null,
"requests": [
{
"money": NumberDecimal("50000"),
"user": "user1"
},
{
"money": NumberDecimal("50000"),
"user": "user3"
}
]
},
]
You have to use an aggregation pipeline like this:
db.campaign.aggregate([
{
$set: {
remainder: {
$subtract: [ "$limit", { $sum: "$requests.money" } ]
}
}
},
{
"$match": {
$or: [
{ limit: null },
{ remainder: { $gte: 0 } }
]
}
},
{ $unset: "remainder" }
])
Mongo Playground
This one is also possible, but more difficult to read:
db.campaign.aggregate([
{
"$match": {
$or: [
{ limit: null },
{
$expr: {
$gt: [
{ $subtract: [ "$limit", { $sum: "$requests.money" } ] },
0
]
}
}
]
}
}
])

mongodb query update select nested fields

this is my document in mongo:
"calendar": {
"_id": "5cd26a886458720f7a66a3b8",
"hotel": "5cd02fe495be1a4f48150447",
"calendar": [
{
"_id": "5cd26a886458720f7a66a413",
"date": "1970-01-01T00:00:00.001Z",
"rooms": [
{
"_id": "5cd26a886458720f7a66a415",
"room": "5cd17d82ca56fe43e24ae5d3",
"price": "",
"remaining": 0,
"reserved": 0
},
{
"_id": "5cd26a886458720f7a66a414",
"room": "5cd17db6ca56fe43e24ae5d4",
"price": "",
"remaining": 0,
"reserved": 0
}
]
},
}
I need to update the objects in the inner rooms array . I tried a query that selects a matching element no syntax error but an error comes in:
"errmsg" : "The field 'calendar.0.rooms.0.price' must be an array but
is of type string in document {_id:
ObjectId('5cd26a886458720f7a66a3b8')}",
and this my query:
db.calendars.updateOne({_id:ObjectId("5cd26a886458720f7a66a3b8"),
"calendar":{"$elemMatch":{"_id":ObjectId("5cd26a886458720f7a66a413"),"rooms._id":
ObjectId("5cd26a886458720f7a66a415")}}},
{"$push":{"calendar.$[outer].rooms.$[inner].price":"100000"}}, {"arrayFilters":[{"outer._id":ObjectId("5cd26a886458720f7a66a413")},{"inner._id":ObjectId("5cd26a886458720f7a66a415")}]})
this is some reference I found in StackOverflow but not helped:
Updating a Nested Array with MongoDB
You can use below query
db.getCollection("test").updateOne(
{
"_id": ObjectId("5cd26a886458720f7a66a3b8"),
"calendar.calendar": {
"$elemMatch": {
"_id": ObjectId("5cd26a886458720f7a66a413"),
"rooms._id": ObjectId("5cd26a886458720f7a66a415")
}
}
},
{ "$set": { "calendar.calendar.$[outer].rooms.$[inner].price": "100000" } },
{
"arrayFilters": [
{ "outer._id": ObjectId("5cd26a886458720f7a66a413") },
{ "inner._id": ObjectId("5cd26a886458720f7a66a415") }
]
}
)
I will update my answer with some explanation afterward

Fetch mongo documents based on multiple fields

Given mongo document of the following form in a collection:
{
"_id":"ObjectId",
"value":{
"id": 1,
"payment": [
{
"status": {
"id": "1.1",
"value": "Paid"
}
},
{
"status": {
"id": "1.2",
"value": "Scheduled"
}
},
{
"status": {
"id": "1.3",
"value": "Recorded"
}
}
]
}
}
ids = [1,2,3,4]
How can i fetch all documents having id in ids and at least one of payments.status.value equal to Scheduled state ?
I am using the following query but it's returning 0 records,
db.collectionName.find({$and:[{"value.id":{$in:ids}},{"value.payment.status.value":"Scheduled"}]})`
you can specify the name of the collection
so instead of this:
db.collection.find({
$and:
[
{"value.id": {$in:ids}},
{"value.payment.status.value":"Scheduled"}
]
})
you can write:
db.payments.find({
$and:
[
{"value.id":{$in:ids}},
{"value.payment.status.value":"Scheduled"}
]
})
db.collection.find({
'value.payment': {
$elemMatch: {
'status.value': 'Scheduled'
}
},
'value.id': {
$in: [1, 2, 3, 4]
}
}, {
'value.id': 1,
'value.payment.$.status': 1
})

MongoDB select distinct and count

I have a product collection which looks like that:
products = [
{
"ref": "1",
"facets": [
{
"type":"category",
"val":"kitchen"
},
{
"type":"category",
"val":"bedroom"
},
{
"type":"material",
"val":"wood"
}
]
},
{
"ref": "2",
"facets": [
{
"type":"category",
"val":"kitchen"
},
{
"type":"category",
"val":"livingroom"
},
{
"type":"material",
"val":"plastic"
}
]
}
]
I would like to select and count the distinct categories and the number of products that have the category (Note that a product can have more than one category). Something like that:
[
{
"category": "kitchen",
"numberOfProducts": 2
},
{
"category": "bedroom",
"numberOfProducts": 1
},
{
"category": "livingroom",
"numberOfProducts": 1
}
]
And it would be better if I could get the same result for each different facet type, something like that:
[
{
"facetType": "category",
"distinctValues":
[
{
"val": "kitchen",
"numberOfProducts": 2
},
{
"val": "livingroom",
"numberOfProducts": 1
},
{
"val": "bedroom",
"numberOfProducts": 1
}
]
},
{
"facetType": "material",
"distinctValues":
[
{
"val": "wood",
"numberOfProducts": 1
},
{
"val": "plastic",
"numberOfProducts": 1
}
]
}
]
I am doing tests with distinct, aggregate and mapReduce. But can't achieve the results needed. Can anybody tell me the good way?
UPDATE:
With aggregate, this give me the different facet categories that a product have, but not the values nor the count of different values:
db.products.aggregate([
{$match:{'content.facets.type':'category'}},
{$group:{ _id: '$content.facets.type'} }
]).pretty();
The following aggregation pipeline will give you the desired result. In the first pipeline step, you need to do an $unwind operation on the facets array so that it's deconstructed to output a document for each element. After the $unwind stage is the first of the $group operations which groups the documents from the previous stream by category and type and calculates the number of products in each group using $sum. The next $group operation in the next pipeline stage then creates the array that holds the aggregated values by using $addToSet operator. The final pipeline stage is the $project operation which then transforms the document in the stream by modifying existing fields:
var pipeline = [
{ "$unwind": "$facets" },
{
"$group": {
"_id": {
"facetType": "$facets.type",
"value": "$facets.val"
},
"count": { "$sum": 1 }
}
},
{
"$group": {
"_id": "$_id.facetType",
"distinctValues": {
"$addToSet": {
"val": "$_id.value",
"numberOfProducts": "$count"
}
}
}
},
{
"$project": {
"_id": 0,
"facetType": "$_id",
"distinctValues": 1
}
}
];
db.product.aggregate(pipeline);
Output
/* 0 */
{
"result" : [
{
"distinctValues" : [
{
"val" : "kitchen",
"numberOfProducts" : 2
},
{
"val" : "bedroom",
"numberOfProducts" : 1
},
{
"val" : "livingroom",
"numberOfProducts" : 1
}
],
"facetType" : "category"
},
{
"distinctValues" : [
{
"val" : "wood",
"numberOfProducts" : 1
},
{
"val" : "plastic",
"numberOfProducts" : 1
}
],
"facetType" : "material"
}
],
"ok" : 1
}