How can I query the same element of an embedded array - mongodb

I have a database with documents such as this one. I want to create an aggregation that will return me only document where participants.stats.win = true and participants.championId = 57. I need both condition to be true for the same participant.
[{$match: {
$and: [ { "participants.stats.win": {$eq: true} },
{ "participants.championId": { $eq: 57 } },
{ gameMode: {$eq: "CLASSIC"}}
]
}}, {$project: {
participants: {
$filter: {
input: "$participants",
as: "participant",
cond: {
$eq: ["$$participant.championId", 57 ]
}
}
}
}}, {$group: {
_id: null,
count: {
$sum: 1
}
}}]
This will return me documents in which a participant.championId = 57 whether or not that said participant won or lost his game.

Related

How to transform an array field into a value equaling its maximum?

{
name: "use_name",
grades: [
{class: "math": grade: 100},
{class: "english": grade: 90}
]
}
How do I write an aggregation pipeline to output:
{
name: "use_name",
grades: {class: "math": grade: 100},
}
The grades field has been reduced to the element where its grade property is the maximum of all elements.
The requirements, the aggregation pipeline cannot have $unwind or $group because it cannot have a stage where the stage needs to receive all incoming documents before outputting to the next stage, potentially exceeding the 100mb limit. And it must be fast.
I think this one is faster:
db.collection.aggregate([
{
$set: {
grades: {
$first: {
$sortArray: {
input: "$grades",
sortBy: { grade: -1 }
}
}
}
}
}
])
Mongo Playground
or this one:
db.collection.aggregate([
{
$set: {
grades: {
$filter: {
input: "$grades",
cond: { $eq: [ "$$this.grade", { $max: "$grades.grade" } ] }
}
}
}
}
])
Replace $$value in $reduce until you find the max.
db.collection.aggregate([
{
$set: {
grades: {
"$reduce": {
"input": "$grades",
"initialValue": null,
"in": {
"$cond": {
"if": {
$or: [
{
$eq: [
null,
"$$value"
]
},
{
$gt: [
"$$this.grade",
"$$value.grade"
]
}
]
},
"then": "$$this",
"else": "$$value"
}
}
}
}
}
}
])
Mongo Playground

Mongodb - Perform calculation with a just deleted value in aggregation pipeline

I have this document:
{
_id: ObjectId('asdu7329n'),
payments: [
{ _id: ObjectId('28sdf310'), paidAmount: 20 },
{ _id: ObjectId('2fsd410'), paidAmount: 15 },
{ _id: ObjectId('2fs32gd70'), paidAmount: 35 },
],
totalPaidAmount: 70
}
What I want is to re-calculate the totalPaidAmount field when a payment is removed, right now I'm deleting the payment in this way:
const query = { _id: ObjectId(saleId) };
const update = [
{ $set: { payments: { $filter: {
input: '$payments',
cond: {
$ne: [ "$$this._id", ObjectId(/* paymentId to delete */) ]
}
}}}}
]
await salesSchema.findOneAndUpdate(query, update);
I know that I have to use $subtract possibly in a second $set stage but how could I reference the paidAmount value from the object so that I can do something like this:
{
$set: {
totalPaidAmount: {
$subtract: [ '$totalPaidAmount', /* paidAmount value for the deleted payment */ ]
}
}
}
I know that I can just sum the paidAmount values for all the indexes of payments but what if there is like 1000 or more items? even if it doesn't hit the performance too much it seems to me more logical to take advantage of the totalPaidAmount field here.
If you want to subtract you can use the $filter:
db.collection.update(
{payments: {$elemMatch: {_id: ObjectId("63920f965d15e98e3d7c452c")}}},
[{$project: {
payments: {
$filter: {
input: "$payments",
cond: {$ne: ["$$this._id", ObjectId("63920f965d15e98e3d7c452c")]}
}
},
totalPaidAmount: {
$subtract: [
"$totalPaidAmount",
{$getField: {
input: {
$first: {
$filter: {
input: "$payments",
cond: {$eq: ["$$this._id", ObjectId("63920f965d15e98e3d7c452c")]}
}
}
},
field: "paidAmount"
}
}
]
}
}}
])
See how it works on the playground example
But I would go with the good old $sum:
db.collection.update(
{payments: {$elemMatch: {_id: ObjectId("63920f965d15e98e3d7c452c")}}},
[{$project: {
payments: {
$filter: {
input: "$payments",
cond: {$ne: ["$$this._id", ObjectId("63920f965d15e98e3d7c452c")]}
}
}
}},
{$set: {totalPaidAmount: {$sum: "$payments.paidAmount"}}}
])
See how it works on the playground example

Conditional match on an existence of a field in collection

Please help me. I have a collection as below
[
{
"_id":{
"$oid":"62a3673660e2f16c7a7bc088"
},
"merchant":{
"$oid":"62a3640560e2f16c7a7bc078"
},
"title":"24 Test 1",
"filter_conditions":{
"city":[
"AAA",
"BBB",
"CCC",
"DDD"
],
"state":[
],
"pincode":[
"12345"
]
}
},
{
"_id":{
"$oid":"62a3673660e2f16c7a7bc089"
},
"merchant":{
"$oid":"62a3640560e2f16c7a7bc079"
},
"title":"24 Test 2",
"filter_conditions":{
"city":[
"AAA",
"BBB"
]
}
}
]
I want to filter data based on pincode/city/state
if pincode is present match it and ignore city and state
elseif city is present match it and ignore state
else match on state
You can use an aggregation pipeline with a $filter:
If any of the fields does not exist on the doc, create it with an empty array.
Use $filter to grade the docs, so the grade for matching pincode is 100, for matching city is 10 for matching state is 1. Use $max to keep the best grade only.
Return the doc with highest grade.
db.collection.aggregate([
{$set: {
"filter_conditions.pincode": {$ifNull: ["$filter_conditions.pincode", []]},
"filter_conditions.city": {$ifNull: ["$filter_conditions.city", []]},
"filter_conditions.state": {$ifNull: ["$filter_conditions.state", []]}
}
},
{$set: {
grade: {
$max: [
{$multiply: [
{$size: {
$filter: {
input: "$filter_conditions.pincode",
as: "item",
cond: {$eq: ["$$item", "12345"]}
}
}
}, 100]
},
{$multiply: [
{$size: {
$filter: {
input: "$filter_conditions.city",
as: "item",
cond: {$eq: ["$$item", "BBB"]}
}
}
}, 10]
},
{$multiply: [
{$size: {
$filter: {
input: "$filter_conditions.state",
as: "item",
cond: {$eq: ["$$item", "AL"]}
}
}
}, 1]
}
]
}
}
},
{$sort: {grade: -1}},
{$limit: 1}
])
See how it works on the playground example
You can work with nested $cond to perform the filtering.
Concept:
Check filter_conditions.pincode is existed.
1.1. If true, check the value is existed in filter_conditions.pincode array.
1.2. Else, proceed to 2.
Check filter_conditions.city is existed.
2.1. If true, check the value is existed in filter_conditions.city array.
2.2. Else, proceed to 3.
Check if value is existed in filter_conditions.state array (default as empty array if the array is not existed).
db.collection.aggregate([
{
$match: {
$expr: {
$cond: {
if: {
$ne: [
"$filter_conditions.pincode",
undefined
]
},
then: {
$in: [
"", // pincode value
"$filter_conditions.pincode"
]
},
else: {
$cond: {
if: {
$ne: [
"$filter_conditions.city",
undefined
]
},
then: {
$in: [
"", // city value
"$filter_conditions.city"
]
},
else: {
$in: [
"", // state value
{
$ifNull: [
"$filter_conditions.state",
[]
]
}
]
}
}
}
}
}
}
}
])
Sample Mongo Playground

MongoDB - Aggregate get specific objects in an array

How can I get only objects in the sales array matching with 2021-10-14 date ?
My aggregate query currently returns all objects of the sales array if at least one is matching.
Dataset Documents
{
"name": "#0",
"sales": [{
"date": "2021-10-14",
"price": 3.69,
},{
"date": "2021-10-15",
"price": 2.79,
}]
},
{
"name": "#1",
"sales": [{
"date": "2021-10-14",
"price": 1.5,
}]
}
Aggregate
{
$match: {
sales: {
$elemMatch: {
date: '2021-10-14',
},
},
},
},
{
$group: {
_id: 0,
data: {
$push: '$sales',
},
},
},
{
$project: {
data: {
$reduce: {
input: '$data',
initialValue: [],
in: {
$setUnion: ['$$value', '$$this'],
},
},
},
},
}
Result
{"date": "2021-10-14","price": 3.69},
{"date": "2021-10-15","price": 2.79},
{"date": "2021-10-14","price": 1.5}
Result Expected
{"date": "2021-10-14","price": 3.69},
{"date": "2021-10-14","price": 1.5}
You actually need to use a $replaceRoot or $replaceWith pipeline which takes in an expression that gives you the resulting document filtered using $arrayElemAt (or $first) and $filter from the sales array:
[
{ $match: { 'sales.date': '2021-10-14' } },
{ $replaceWith: {
$arrayElemAt: [
{
$filter: {
input: '$sales',
cond: { $eq: ['$$this.date', '2021-10-14'] }
}
},
0
]
} }
]
OR
[
{ $match: { 'sales.date': '2021-10-14' } },
{ $replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: '$sales',
cond: { $eq: ['$$this.date', '2021-10-14'] }
}
},
0
]
}
} }
]
Mongo Playground
In $project stage, you need $filter operator with input as $reduce operator to filter the documents.
{
$project: {
data: {
$filter: {
input: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$setUnion: [
"$$value",
"$$this"
],
}
}
},
cond: {
$eq: [
"$$this.date",
"2021-10-14"
]
}
}
}
}
}
Sample Mongo Playground
How about using $unwind:
.aggregate([
{$match: { sales: {$elemMatch: {date: '2021-10-14'} } }},
{$unwind: '$sales'},
{$match: {'sales.date': '2021-10-14'}},
{$project: {date: '$sales.date', price: '$sales.price', _id: 0}}
])
This will separate the sales into different documents, each containing only one sale, and allow you to match conditions easily.
See: https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

MongoDB count occurances with group and unwind

I have a MongoDB database with the following document structure:
{
"name": "ServiceA",
"areas": ["X", "Y", "Z"],
"tags": [
{
"name": "Financial",
"type": "A"
},
{
"name": "Consumer",
"type": "B"
}
]
}
There's many entries each with the same structure. Containing the same areas.
There's many predefined tag names, sorted into a few types.
The aim is to group by area and then count the number of occurrences of each tag. So an output like this:
{
"area": "X",
"count": 100, // Total entries with X as an area
"tagNameCount": {
"Financial": 20,
"Consumer": 10,
...
},
"tagTypeCount": {
"A": 70,,
"B: 40
}
}
I've been starting of using $unwind on areas, but it's the next steps from there I'm stuck on. I get that I need to use $group, but I can't work out how to count occurrences.
You may use $facet operator which allows perform several aggregation in one.
Walkthrough
1. We $unwind by area and tags
2. With $facet, we perform 3 parallel aggregations:
2.1 We count unique areas
2.2 We count unique tag names for each area
2.3 We count unique tag type for each area
3. We join 2 parallel arrays by flatten areas
4. We assemble desired output
db.collection.aggregate([
{
$unwind: "$areas"
},
{
$unwind: "$tags"
},
{
$facet: {
areas: [
{
$group: {
_id: "$areas",
count: {
$addToSet: "$_id"
}
}
},
{
$project: {
_id: 0,
area: "$_id",
count: {
$size: "$count"
}
}
}
],
tagNameCount: [
{
$group: {
_id: {
name: "$tags.name",
areas: "$areas"
},
count: {
$addToSet: "$_id"
}
}
},
{
$group: {
_id: "$_id.areas",
tagNameCount: {
$push: {
k: "$_id.name",
v: {
$size: "$count"
}
}
}
}
},
{
$addFields: {
tagNameCount: {
$arrayToObject: "$tagNameCount"
}
}
}
],
tagTypeCount: [
{
$group: {
_id: {
type: "$tags.type",
areas: "$areas"
},
count: {
$addToSet: "$_id"
}
}
},
{
$group: {
_id: "$_id.areas",
tagTypeCount: {
$push: {
k: "$_id.type",
v: {
$size: "$count"
}
}
}
}
},
{
$addFields: {
tagTypeCount: {
$arrayToObject: "$tagTypeCount"
}
}
}
]
}
},
{
$unwind: "$areas"
},
{
$addFields: {
"tagNameCount": {
$filter: {
input: "$tagNameCount",
cond: {
$eq: [
"$areas.area",
"$$this._id"
]
}
}
},
"tagTypeCount": {
$filter: {
input: "$tagTypeCount",
cond: {
$eq: [
"$areas.area",
"$$this._id"
]
}
}
}
}
},
{
$project: {
area: "$areas.area",
count: "$areas.count",
tagNameCount: {
$arrayElemAt: [
"$tagNameCount.tagNameCount",
0
]
},
tagTypeCount: {
$arrayElemAt: [
"$tagTypeCount.tagTypeCount",
0
]
}
}
},
{
$sort: {
area: 1
}
}
])
MongoPlayground
Here's one method:
unwind both areas and tags
for each area collect the applicable tags, and the unique names and types
count the names to get the total number of tags
for each unique name, count the matching values in the tags
do the same for each unique type
project out the unique fields
db.collection.aggregate([
{$unwind: "$areas"},
{$unwind: "$tags"},
{$group: {
_id: "$areas",
names: {$push: "$tags.name"},
uniqueNames: {$addToSet: "$tags.name"},
types: {$push: "$tags.type"},
uniqueTypes: {$addToSet: "$tags.type"}
}},
{$addFields: {
count: {$size: "$names"},
names: {
$arrayToObject: {
$map: {
input: "$uniqueNames",
as: "needle",
in: {
k: "$$needle",
v: {
$size: {
$filter: {
input: "$names",
cond: {$eq: ["$$this","$$needle"]}
}}}}}}},
types: {
$arrayToObject: {
$map: {
input: "$uniqueTypes",
as: "needle",
in: {
k: "$$needle",
v: {$size: {
$filter: {
input: "$types",
cond: { $eq: [ "$$this","$$needle"]}
}}}}}}}}},
{
$project: {
uniqueNames: 0,
uniqueTypes: 0
}}
])
Playground