Could you give me advise? I have a document like this:
{
"_id" : ObjectId("569620270d3ac01895316edb"),
"customerId" : NumberLong("2000900000000000022"),
"gender" : "MALE",
"birthDate" : ISODate("1976-01-06T23:00:00Z"),
"someArray" : [
{
"id" : 5411,
"firstDate" : ISODate("2014-08-05T16:17:50Z"),
"lastDate" : ISODate("2015-10-31T11:55:51Z"),
"sumOfAll" : 5677.35,
"minAmount" : 9.75,
"maxAmount" : 231.72,
"innerArray" : [
{
"count" : 4,
"amount" : 449.33
},
{
"count" : 3,
"amount" : 401.31
},
{
"count" : 7,
"amount" : 617.8000000000001
},
{
"count" : 4,
"amount" : 465.28999999999996
},
{
"count" : 2,
"amount" : 212.95999999999998
},
{
"count" : 4,
"amount" : 497.53999999999996
},
{
"count" : 3,
"amount" : 278.23
},
{
"count" : 3,
"amount" : 383.15999999999997
},
{
"count" : 6,
"amount" : 459.63
},
{
"count" : 9,
"amount" : 677.19
},
{
"count" : 4,
"amount" : 393.85
}
]
},
{
"id" : 5812,
"firstDate" : ISODate("2014-09-03T17:16:32Z"),
"lastDate" : ISODate("2015-11-04T22:59:59Z"),
"sumOfAll" : 275.6,
"minAmount" : 15,
"maxAmount" : 69,
"innerArray" : [
{
"count" : 1,
"amount" : 17
},
{
"count" : 1,
"amount" : 15.4
},
{
"count" : 1,
"amount" : 69
},
{
"count" : 1,
"amount" : 53.7
},
{
"count" : 2,
"amount" : 84
}
]
},
{
"id" : 7399,
"firstDate" : ISODate("2015-01-12T22:59:59Z"),
"lastDate" : ISODate("2015-03-16T22:59:59Z"),
"sumOfAll" : 144.73,
"minAmount" : 0.84,
"maxAmount" : 24.98,
"innerArray" : [
{
"count" : 5,
"amount" : 50.379999999999995
},
{
"count" : 5,
"amount" : 55.45
},
{
"count" : 10,
"amount" : 38.900000000000006
}
]
},
]
}
And I'd like to filter both inner arrays and also project them. I'm trying this query:
db.sandbox.aggregate([
{ $match: {
'gender': {$eq : 'MALE'},
$or: [
{ $and: [{'someArray.id': {$eq: 5411}}, {'someArray.innerArray.count': 4}, {'someArray.innerArray.amount': {$gte: 2}}]},
{ $and: [{'someArray.id': {$eq: 5812}}, {'someArray.innerArray.count': 5}, {'someArray.innerArray.amount': {$gte: 50}}]},
]
}
},
{ $project: {
gender: 1,
customerId: 1,
someArray: { $filter: {
input: '$someArray',
as: 'item',
cond: {
$and: [
{ $or: [
{$and: [{$eq: ['$$item.id', 5411]}, {$eq: ['$$item.innerArray.count', 4]}, {$gte: ['$$item.innerArray.amount', 2]}]},
{$and: [{$eq: ['$$item.id', 5812]}, {$eq: ['$$item.innerArray.count', 5]}, {$gte: ['$$item.innerArray.amount', 50]}]},
]},
]
}
}},
}}
]).pretty()
And I received result without data in someArray:
{
"_id" : ObjectId("569620270d3ac01895316edb"),
"customerId" : NumberLong("2000900000000000022"),
"gender" : "MALE",
"someArray" : [ ]
}
I want to receive:
{
"_id" : ObjectId("569620270d3ac01895316edb"),
"customerId" : NumberLong("2000900000000000022"),
"gender" : "MALE",
"birthDate" : ISODate("1976-01-06T23:00:00Z"),
"someArray" : [
{
"id" : 5411,
"firstDate" : ISODate("2014-08-05T16:17:50Z"),
"lastDate" : ISODate("2015-10-31T11:55:51Z"),
"sumOfAll" : 5677.35,
"minAmount" : 9.75,
"maxAmount" : 231.72,
"innerArray" : [
{
"count" : 4,
"amount" : 449.33
},
{
"count" : 4,
"amount" : 465.28999999999996
},
{
"count" : 4,
"amount" : 497.53999999999996
},
{
"count" : 4,
"amount" : 393.85
}
]
}
]
}
If I change $eq to $gte, I will receive receive result, but I want to project innerArray too. How can I implement this? Should I use my own MapReduce job or I will able to do this with Aggregation pipeline?
MongoDB version 3.2. Also I observe when I'm trying to use several predicates for array and project only one element, for example:
db.sandbox.find( {$and: [{'someArray.id': 7399}, {'someArray.sumOfAll': {$gte: 5000}}]}, {'customerId': 1, 'someArray.$': 1}).pretty()
But it returns me:
{
"_id" : ObjectId("569620270d3ac01895316edb"),
"customerId" : NumberLong("2000900000000000022"),
"someArray" : [
{
"id" : 5411,
"firstDate" : ISODate("2014-08-05T16:17:50Z"),
"lastDate" : ISODate("2015-10-31T11:55:51Z"),
"sumOfAll" : 5677.35,
"minAmount" : 9.75,
"maxAmount" : 231.72,
"innerArray" : [
{
"count" : 4,
"amount" : 449.33
},
{
"count" : 3,
"amount" : 401.31
},
{
"count" : 7,
"amount" : 617.8000000000001
},
{
"count" : 4,
"amount" : 465.28999999999996
},
{
"count" : 2,
"amount" : 212.95999999999998
},
{
"count" : 4,
"amount" : 497.53999999999996
},
{
"count" : 3,
"amount" : 278.23
},
{
"count" : 3,
"amount" : 383.15999999999997
},
{
"count" : 6,
"amount" : 459.63
},
{
"count" : 9,
"amount" : 677.19
},
{
"count" : 4,
"amount" : 393.85
}
]
}
]
}
Which is incorrect for my perspective. I expect nothing.
First, the way you're using conditions in $match will not result in what you want.
{ $and: [{'someArray.id': {$eq: 5411}}, {'someArray.innerArray.count': 4}, {'someArray.innerArray.amount': {$gte: 2}}]}
The line above will verify each condition separately, instead of checking the count and amount conditions together for each innerArray element. If that's what you want, you should look into the $elemMatch operator.
Second, I don't believe you can use $filter like that on a second-level array. You should unwind someArray first:
db.sandbox.aggregate(
{
$match:
{
gender: { $eq: 'MALE' },
"someArray.id":
{
$in: [5411, 5812]
}
}
},
{
$unwind: "$someArray",
},
{
$project:
{
gender: 1,
customerId: 1,
someArray:
{
id: 1,
firstDate: 1,
lastDate: 1,
sumOfAll: 1,
minAmount: 1,
maxAmount: 1,
innerArray:
{
$filter:
{
input: '$someArray.innerArray',
as: 'item',
cond:
{
$or:
[
{
$and:
[
{ $eq: ['$$item.count', 4] },
{ $gte: ['$$item.amount', 2] }
]
},
{
$and:
[
{ $eq: ['$$item.count', 5] },
{ $gte: ['$$item.amount', 50] }
]
}
]
}
}
}
},
}
})
You can also $group someArray elements back if you want.
Related
Player collection:
{ "_id" : 1, "Name" : "John Aims", "Gender" : "M", "DoB" : ISODate("1990-01-01T00:00:00Z"), "Nationality" : "USA", "Hand" : "R", "YearTurnedPro" : 2010, "Tournament" : [ { "tournamentID" : 1, "TournamentYear" : 2016 }, { "tournamentID" : 2, "TournamentYear" : 2019 }, { "tournamentID" : 3, "TournamentYear" : 2021 } ] }
{ "_id" : 2, "Name" : "George Brown", "Gender" : "M", "DoB" : ISODate("1997-03-04T00:00:00Z"), "Nationality" : "GB", "Hand" : "L", "YearTurnedPro" : 2013, "Tournament" : [ { "tournamentID" : 2, "TournamentYear" : 2016 }, { "tournamentID" : 5, "TournamentYear" : 2019 } ] }
Tournament collection:
{ "_id" : ObjectId("626c18a3d880647a888888ff"), "TournamentID" : 1, "TournamentCode" : "GS1", "Position" : 8, "PrizeMoney" : 125000, "RankingPoints" : 250 }
{ "_id" : ObjectId("626c18c2d880647a888888ff"), "TournamentID" : 2, "TournamentCode" : "GS1", "Position" : 4, "PrizeMoney" : 250000, "RankingPoints" : 500 }
{ "_id" : ObjectId("626c18ddd880647a888888ff"), "TournamentID" : 3, "TournamentCode" : "GS1", "Position" : 1, "PrizeMoney" : 1000000, "RankingPoints" : 2000 }
1st Question:
Hello, I want to get the sum of ranking points of each player.
I have tried:
db.Player.aggregate([
{"$unwind" : "$Tournament"},
{"$lookup":
{"from":"Tournament",
"localField":"Tournament.tournamentID",
"foreignField":"TournamentID",
"as":"Tennis-player"}},
{ "$group": {
"_id": { Name:"$Name" },
"total_qty": { "$sum": "$Tennis-player.PrizeMoney" }
}}
])
But I get for every played the sum is 0.
I can show it on playground as it is using more than 1 collection.
2nd question:
Would it be better to create only 1 collections with all the data?
$unwind
$lookup
$set - As from stage 2 Tennis-player returns an array with guarantee only 1 document in array. Use $first to get the first document in Tennis-player array field to become a document field.
$group
db.Player.aggregate([
{
"$unwind": "$Tournament"
},
{
"$lookup": {
"from": "Tournament",
"localField": "Tournament.tournamentID",
"foreignField": "TournamentID",
"as": "Tennis-player"
}
},
{
$set: {
"Tennis-player": {
"$first": "$Tennis-player"
}
}
},
{
"$group": {
"_id": {
Name: "$Name"
},
"total_qty": {
"$sum": "$Tennis-player.PrizeMoney"
}
}
}
])
Sample Mongo Playground
Alternative:
$lookup - Work $lookup with an Array
$project - Decorate output documents. Create total_qty field and use $reduce to perform sum operation of Tennic-player.PrizeMoney.
db.Player.aggregate([
{
"$lookup": {
"from": "Tournament",
"localField": "Tournament.tournamentID",
"foreignField": "TournamentID",
"as": "Tennis-player"
}
},
{
"$project": {
"_id": {
Name: "$Name"
},
"total_qty": {
"$reduce": {
"input": "$Tennis-player",
"initialValue": 0,
"in": {
$sum: [
"$$value",
"$$this.PrizeMoney"
]
}
}
}
}
}
])
Sample Mongo Playground (Alternative)
My database has 3 Collections :
Tour, 2) turism_ind, 3) customer
> db.tour.find({}).pretty()
{
"_id" : ObjectId("622385ab1b68d9136e48ba51"),
"source" : "Pune",
"destination" : "Kashmir"
}
{
"_id" : ObjectId("622385ba1b68d9136e48ba52"),
"source" : "Mumbai",
"destination" : "Shilong"
}
{
"_id" : ObjectId("622385ce1b68d9136e48ba53"),
"source" : "Nashik",
"destination" : "Goa"
}
> db.turism_ind.find({}).pretty()
{
"_id" : ObjectId("6223885d1b68d9136e48ba57"),
"ind_name" : "Veena World",
"package" : [
{
"pkg_id" : 111,
"tour_id" : ObjectId("622385ba1b68d9136e48ba52"),
"cost" : 85000
}
],
"cust_review" : [
{
"cust_id" : ObjectId("622387fa1b68d9136e48ba56"),
"rating" : 4
}
]
}
{
"_id" : ObjectId("622389191b68d9136e48ba58"),
"ind_name" : "GK Travels",
"package" : [
{
"pkg_id" : 222,
"tour_id" : ObjectId("622385ba1b68d9136e48ba52"),
"cost" : 82000
},
{
"pkg_id" : 223,
"tour_id" : ObjectId("622385ab1b68d9136e48ba51"),
"cost" : 78000
}
],
"cust_review" : [
{
"cust_id" : ObjectId("622387f51b68d9136e48ba55"),
"rating" : 5
}
]
}
{
"_id" : ObjectId("622389ae1b68d9136e48ba59"),
"ind_name" : "KK Tours",
"package" : [
{
"pkg_id" : 333,
"tour_id" : ObjectId("622385ce1b68d9136e48ba53"),
"cost" : 57000
},
{
"pkg_id" : 334,
"tour_id" : ObjectId("622385ab1b68d9136e48ba51"),
"cost" : 79000
}
],
"cust_review" : [
{
"cust_id" : ObjectId("622387f51b68d9136e48ba55"),
"rating" : 5
},
{
"cust_id" : ObjectId("622387ef1b68d9136e48ba54"),
"rating" : 4
}
]
}
> db.customer.find({}).pretty()
{
"_id" : ObjectId("622387ef1b68d9136e48ba54"),
"cust_name" : "Aniket",
"selected_pkg" : [
111
]
}
{
"_id" : ObjectId("622387f51b68d9136e48ba55"),
"cust_name" : "Nik",
"selected_pkg" : [
222,
333,
334
]
}
{
"_id" : ObjectId("622387fa1b68d9136e48ba56"),
"cust_name" : "Sham",
"selected_pkg" : [
111,
222
]
}
{
"_id" : ObjectId("62238c671b68d9136e48ba5a"),
"cust_name" : "John",
"selected_pkg" : [
111,
222,
223,
333,
334
]
}
I want to perform the following queries :
1] List all the details of expenses made by John on his first 3 trips.
Also display the total expenses
2] List the names of the customers who went on a tour to Shillong. [5]
It is possible to perform those queries on the given document or document structure is wrong?
I try this for 1st query
db.turism_ind.aggregate(
{$unwind : "$package"},
{$match : { "package.pkg_id" : {$in : [111,222,333] } } },
{$project :{ _id : 0 , package : 1, total_cost : {$sum : "$package.cost"} }}
)
Output (Not Correct)
{ "package" : { "pkg_id" : 111, "tour_id" : ObjectId("622385ba1b68d9136e48ba52"), "cost" : 85000 }, "total_cost" : 85000 }
{ "package" : { "pkg_id" : 222, "tour_id" : ObjectId("622385ba1b68d9136e48ba52"), "cost" : 82000 }, "total_cost" : 82000 }
{ "package" : { "pkg_id" : 333, "tour_id" : ObjectId("622385ce1b68d9136e48ba53"), "cost" : 57000 }, "total_cost" : 57000 }
Here is the aggregation for your first part. There might be some bug depending on your other documents inside your collections.
Here's a quick breakdown of your question.
List all the details of expenses made by John
Utilize $match to get john document
on his first 3 trips.
Utilize $project to slice the first 3 elements out and the do a $lookup on the turism_ind to grab the necessary documents
Also display the total expenses
Had to utilize the $reduce in a projection to flatten 2D array into 1D, and follow by another project to $sum all the cost together as total.
MongoDB Playground
db.customer.aggregate([
{
"$match": {
"cust_name": "John"
}
},
{
"$project": {
"firstThree": {
"$slice": [
"$selected_pkg",
0,
3
]
}
}
},
{
"$lookup": {
"from": "turism_ind",
"localField": "firstThree",
"foreignField": "package.pkg_id",
"as": "tour"
}
},
{
"$project": {
"_id": 0,
"packages": {
$reduce: {
input: "$tour.package",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
"$project": {
"packages": 1,
"total": {
"$sum": "$packages.cost"
}
}
}
])
Result
[
{
"packages": [
{
"cost": 85000,
"pkg_id": 111,
"tour_id": ObjectId("622385ba1b68d9136e48ba52")
},
{
"cost": 82000,
"pkg_id": 222,
"tour_id": ObjectId("622385ba1b68d9136e48ba52")
},
{
"cost": 78000,
"pkg_id": 223,
"tour_id": ObjectId("622385ab1b68d9136e48ba51")
}
],
"total": 245000
}
]
I am trying to update a field inside array of objects, where field in nested array is equal to a value.
My goal here is to set the picture field a new url, where value field in valueList is oldRed
Product schema:
{
variations: [{
id: 1,
picture: 'https://example.picture.com',
valueList: [{
name: 'color',
value: 'oldRed'
}, {
name: 'size',
value: 'M'
}]
}, {
id: 2,
picture: 'https://example.picture.com',
valueList: [{
name: 'color',
value: 'black'
}, {
name: 'size',
value: 'M'
}]
}]
}
The closest I get is thanks to this answer, where I update all my nested array fields that contains :'oldRed' . But my final goal is to update other field picture, based on nested array field.
db.Product.findOneAndUpdate({
_id: '123'
}, {
$set: {
'variations.$[].valueList.$[nameField].value': 'newRed'
}
}, {
arrayFilters: [{
'nameField.value': 'oldRed'
}],
new: true
}
});
Please try this :
db.Product.findOneAndUpdate(
{ _id: 123 },
{
$set: {
'variations.$[item].valueList.$[nameField].value': 'newRed',
'variations.$[item].picture': 'newURL' // item is each object in variations which is being checked in arrayFilters.
}
},
{
arrayFilters: [{ 'item.valueList.value': 'oldRed' }, { 'nameField.value': 'oldRed' }],
new: true
}
)
Colletion Data :
{
"_id" : 123,
"variations" : [
{
"id" : 1,
"picture" : "https://example.picture.com",
"valueList" : [
{
"name" : "color",
"value" : "oldRed"
},
{
"name" : "size",
"value" : "M"
},
{
"name" : "color",
"value" : "oldRed"
}
]
},
{
"id" : 2,
"picture" : "https://example.picture.com",
"valueList" : [
{
"name" : "color",
"value" : "black"
},
{
"name" : "size",
"value" : "M"
}
]
},
{
"id" : 3,
"picture" : "https://example3.picture.com",
"valueList" : [
{
"name" : "color",
"value" : "oldRed"
},
{
"name" : "size",
"value" : "M"
}
]
}
]
}
Result :
/* 1 */
{
"_id" : 123,
"variations" : [
{
"id" : 1,
"picture" : "newURL",
"valueList" : [
{
"name" : "color",
"value" : "newRed"
},
{
"name" : "size",
"value" : "M"
},
{
"name" : "color",
"value" : "newRed"
}
]
},
{
"id" : 2,
"picture" : "https://example.picture.com",
"valueList" : [
{
"name" : "color",
"value" : "black"
},
{
"name" : "size",
"value" : "M"
}
]
},
{
"id" : 3,
"picture" : "newURL",
"valueList" : [
{
"name" : "color",
"value" : "newRed"
},
{
"name" : "size",
"value" : "M"
}
]
}
]
}
I was wondering if there is a way to "flatten" though projection a nested sub-document array so I could use it to sum its entries based on type.
My document looks like this:
{
"order_id":12345,
"date":8/17/2019,
"payment":{
status:1,
transactions:[
{type: 1, amount:200},
{type: 2, amount:250},
{type: 3, amount:50},
{type: 4, amount:50},
]
}
}
I would like to see if you can "flatten" it to something like this using $project:
{
"order_id":12345,
"date":8/17/2019,
"status":1,
"type": 1,
"amount":200
},
{
"order_id":12345,
"date":8/17/2019,
"status":1,
"type": 2,
"amount":250
},
{
"order_id":12345,
"date":8/17/2019,
"status":1,
"type": 4,
"amount":50
},
{
"order_id":12345,
"date":8/17/2019,
"status":1,
"type": 4,
"amount":50
}
}
Primarily my goal is to aggregate all the amounts for transactions of type 1 & 3 and all the transactions with type 2 & 4.
Any help would be great.
The following query can get you the expected output:
db.check.aggregate([
{
$unwind:"$payment.transactions"
},
{
$project:{
"_id":0,
"order_id":1,
"date":1,
"status":"$payment.status",
"type":"$payment.transactions.type",
"amount":"$payment.transactions.amount"
}
}
]).pretty()
Output:
{
"order_id" : 12345,
"date" : "8/17/2019",
"status" : 1,
"type" : 1,
"amount" : 200
}
{
"order_id" : 12345,
"date" : "8/17/2019",
"status" : 1,
"type" : 2,
"amount" : 250
}
{
"order_id" : 12345,
"date" : "8/17/2019",
"status" : 1,
"type" : 3,
"amount" : 50
}
{
"order_id" : 12345,
"date" : "8/17/2019",
"status" : 1,
"type" : 4,
"amount" : 50
}
I'm trying to remove data from a sub array as follow but am having difficulties.
{
"_id" : "0",
"mainArray" : [
{
"price" : 12,
"informations" : [
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 1
},
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 2
}
]
},{
"price" : 45,
"informations" : [
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 5
},
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 6
}
]
}
I would like the output to be :
{
"_id" : "0",
"mainArray" : [
{
"price" : 12,
"informations" : [
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 1
},
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 2
}
]
},{
"price" : 45,
"informations" : [
{
"createdBy" : "0x957a1a87d653ea2218742aeea5a05f637b6509c4",
"orderId" : 5
}
]
}
I've tried this :
db.collection.update({ "_id": "0" }, { $pull: { 'mainArray.informations': { "orderId": 6 } } });
and
db.collection.update({ "_id": "0" }, { $pull: { 'mainArray.0.informations': { "orderId": 6 } } });
But both don't work, the best i'm getting :
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
Anyone see where i'm going wrong ?
You need to use the positional operator '$' . $ is a positional operator which identifies an element in an array to update without explicitly specifying the position of the element in the array
db.collection.update({ "_id": "0" ,"mainArray.informations.orderId":6 },
{ $pull: { 'mainArray.$.informations': { "orderId": 6 } } });
Note : the array field must appear as part of the query document to figure out the matching position in mainArray.