I have a mongo collection (store details) like below...
{
_id: "",
storeName: "store1",
items: [
{
itemName: "mongo",
itemPrice: 20,
itemAvailablity: 100
},
{
itemName: "apples",
itemPrice: 50,
itemAvailablity: 70
}
]
},
{
_id: "",
storeName: "store2",
items: [
{
itemName: "banana",
itemPrice: 10,
itemAvailablity: 30
},
{
itemName: "apple",
itemPrice: 45,
itemAvailablity: 90
}
]
},
{
_id: "",
storeName: "store3",
items: [
{
itemName: "apple",
itemPrice: 10,
itemAvailablity: 30
},
{
itemName: "mongo",
itemPrice: 30,
itemAvailablity: 50
}
]
}
from the above data, I want to get particular item details along with storeName.
If I want to get "mongo" details from all stores then my expected output will be like
[
{
itemName: "mongo",
itemPrice: 20,
itemAvailablity: 100,
storeName: "store1"
},
{
itemName: "mongo",
itemPrice: 30,
itemAvailablity: 50,
storeName: "store3"
}
]
I try with different mongo aggregation queries but I didn't get the output as I expect
can anyone help me out of this
thank you
You can achieve this via this aggregation:
db.collection.aggregate([
{
$project: {
storeName: "$$CURRENT.storeName",
items: {
$filter: {
input: "$items",
as: "item",
cond: { $eq: ["$$item.itemName","mongo"] }
}
}
}
},
{ $unwind: "$items" },
{ $addFields: { "items.storeName": "$storeName"} },
{ $replaceRoot: { newRoot: "$items" }}
])
You can see it working here
You can use below aggregation
db.collection.aggregate([
{ "$match": { "items.itemName": "mongo" }},
{ "$unwind": "$items" },
{ "$match": { "items.itemName": "mongo" }},
{ "$addFields": { "items.storeName": "$storeName" }},
{ "$replaceRoot": { "newRoot": "$items" }}
])
MongoPlayground
Or either you can do this way
db.collection.aggregate([
{ "$match": { "items.itemName": "mongo" }},
{ "$addFields": {
"items": {
"$map": {
"input": {
"$filter": {
"input": "$items",
"as": "item",
"cond": { "$eq": ["$$item.itemName", "mongo"]}
}
},
"as": "item",
"in": { "$mergeObjects": ["$$item", { "storeName": "$storeName" }] }
}
}
}},
{ "$unwind": "$items" },
{ "$replaceRoot": { "newRoot": "$items" }}
])
MongoPlayground
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$match: {
items: {
$elemMatch: {
"itemName": "mongo"
}
}
}
},
// Stage 2
{
$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: {
$eq: ["$$item.itemName", 'mongo']
}
}
},
storeName: 1
}
},
// Stage 3
{
$addFields: {
"items.storeName": '$storeName'
}
},
]
);
Related
Am trying to create a poll results aggregation
I have two collections
poll - here is one document
{
"_id": {
"$oid": "636027704f7a15587ef74f26"
},
"question": "question 1",
"ended": false,
"options": [
{
"id": "1",
"option": "option 1"
},
{
"id": "2",
"option": "option 2"
},
{
"id": "3",
"option": "option 3"
}
]
}
Vote - here is one document
{
"_id": {
"$oid": "635ed3210acbf9fd14af8fd1"
},
"poll_id": "636027704f7a15587ef74f26",
"poll_option_id": "1",
"user_id": "1"
}
and i want to perform an aggregate query to get poll results
so am doing the following query
db.vote.aggregate(
[
{
$addFields: {
poll_id: { "$toObjectId": "$poll_id" }
},
},
{
$lookup: {
from: "poll",
localField: "poll_id",
foreignField: "_id",
as: "details"
}
},
{
$group:
{
_id: { poll_id: "$poll_id", poll_option_id: "$poll_option_id" },
details: { $first: "$details" },
count: { $sum: 1 }
}
},
{
$addFields: {
question: { $arrayElemAt: ["$details.question", 0] }
}
},
{
$addFields: {
options: { $arrayElemAt: ["$details.options", 0] }
}
},
{
$group: {
_id: "$_id.poll_id",
poll_id: { $first: "$_id.poll_id" },
question: { $first: "$question" },
options: { $first: "$options" },
optionsGrouped: {
$push: {
id: "$_id.poll_option_id",
count: "$count"
}
},
count: { $sum: "$count" }
}
}
]
)
That is giving me this form of results
{ _id: ObjectId("636027704f7a15587ef74f26"),
poll_id: ObjectId("636027704f7a15587ef74f26"),
question: 'question 1',
options:
[ { id: '1', option: 'option 1' },
{ id: '2', option: 'option 2' },
{ id: '3', option: 'option 3' } ],
optionsGrouped:
[ { id: '1', count: 2 },
{ id: '2', count: 1 } ],
count: 3 }
So what am interested in i want to have the results looking like ( like merging both options & options Group)
{ _id: ObjectId("636027704f7a15587ef74f26"),
poll_id: ObjectId("636027704f7a15587ef74f26"),
question: 'question 1',
optionsGrouped:
[ { id: '1', option: 'option 1', count: 2 },
{ id: '2', option: 'option 2', count: 1 },
{ id: '3', option: 'option 3', count: 0 } ],
count: 4 }
Another question is the DB structure acceptable overall or i can represent that in a better way ?
One option is to group first and use the $lookup later, in order to fetch less data from the poll collection. After the $lookup, use $map with $cond to merge the arrays:
db.vote.aggregate([
{$group: {
_id: {poll_id: {$toObjectId: "$poll_id"}, poll_option_id: "$poll_option_id"},
count: {$sum: 1}
}},
{$group: {
_id: "$_id.poll_id",
counts: {
$push: {count: "$count", option: {$concat: ["option ", "$_id.poll_option_id"]}}
},
countAll: {$sum: "$count"}
}},
{$lookup: {
from: "poll",
localField: "_id",
foreignField: "_id",
as: "poll"
}},
{$project: {poll: {$first: "$poll"}, counts: 1, countAll: 1}},
{$project: {
optionsGrouped: {
$map: {
input: "$poll.options",
in: {$mergeObjects: [
"$$this",
{$cond: [
{$gte: [{$indexOfArray: ["$counts.option", "$$this.option"]}, 0]},
{$arrayElemAt: ["$counts", {$indexOfArray: ["$counts.option", "$$this.option"]}]},
{count: 0}
]}
]}
}
},
count: "$countAll",
question: "$poll.question"
}}
])
See how it works on the playground example
I had reworked the query to match my desires
and this query is achieving the question i have asked
db.poll.aggregate([
{
$addFields: {
_id: {
$toString: "$_id"
}
}
},
{
$lookup: {
from: "poll_vote",
localField: "_id",
foreignField: "poll_id",
as: "votes"
}
},
{
$replaceRoot: {
newRoot: {
$let: {
vars: {
count: {
$size: "$votes"
},
options: {
$map: {
input: "$options",
as: "option",
in: {
$mergeObjects: [
"$$option",
{
count: {
$size: {
$slice: [
{
$filter: {
input: "$votes",
as: "v",
cond: {
$and: [
{
$eq: [
"$$v.poll_option_id",
"$$option._id"
]
}
]
}
}
},
0,
100
]
}
}
},
{
checked: {
$toBool: {
$size: {
$slice: [
{
$filter: {
input: "$votes",
as: "v",
cond: {
$and: [
{
$eq: [
"$$v.user_id",
2
]
},
{
$eq: [
"$$v.poll_option_id",
"$$option._id"
]
}
]
}
}
},
0,
100
]
}
}
}
}
]
}
}
}
},
"in": {
_id: "$_id",
question: "$question",
count: "$$count",
ended: "$ended",
options: "$$options"
}
}
}
}
},
{
$addFields: {
answered: {
$reduce: {
input: "$options",
initialValue: false,
in: {
$cond: [
{
$eq: [
"$$this.checked",
true
]
},
true,
"$$value"
]
}
}
}
}
}
])
I have documents like this:
[
{ id: 1, number: 10 },
{ id: 2, number: 11 },
{ id: 3, number: 12 }
]
How to get documents with combination of other values like example below using aggregation framework?
[
{ id: 1, number: 10, other_numbers: [11, 12] },
{ id: 2, number: 11, other_numbers: [10, 12] },
{ id: 3, number: 12, other_numbers: [10, 11] }
]
db.collection.aggregate([
{
$lookup: {
"from": "collection",
"let": {
"number": "$number"
},
pipeline: [
{
$match: {
$expr: {
$ne: [ "$number", "$$number" ]
}
}
}
],
"as": "other_numbers"
}
},
{
$set: {
"other_numbers": "$other_numbers.number"
}
}
])
mongoplayground
db.collection.aggregate([
{
"$group": {
"_id": null,
"number": {
"$push": "$$ROOT"
},
"other_numbers": {
"$push": "$$ROOT"
}
}
},
{
"$unwind": "$number"
},
{
"$set": {
"other_numbers": {
"$filter": {
"input": "$other_numbers.number",
"as": "i",
"cond": {
"$ne": [
"$$i",
"$number.number"
]
}
}
},
"number": "$number.number",
"_id": "$number.id"
}
}
])
mongoplayground
Problem is that I want to 'enter' array utwor and count average of dlugosc_utworu
How looks my code:
db.artysci.aggregate({
"$project": {
_id: 0,
nazwa: 1,
nazwisko: 1,
"numberOfSongs": {
"$sum": {
"$map": {
"input": "$album",
"in": { "$size": { $ifNull: ["$$this.utwor", []] } }
}
}
},
"avgSongTime":{
"$avg": {
"$map": {
"input": "utwor",
"in": { $ifNull: ["$$this.dlugosc_trwania", []] }
}
}
}
}
})
I want to make this avg of "dlugosc_trwania" who is located in utwor array in album array.
Grid:
db.artysci.insert({
imie: 'Nik',
nazwisko: 'Kershaw',
rok_debiutu: 1983,
kraj_pochodzenia: ['Wielka Brytania'],
gatunek: 'pop',
album: [{
tytul:"Human Racing",
rok_edycji:1990,
gatunek: 'trash metal',
typ_nosnika: 'CD',
utwor: [{
numer: 1,
tytul_utworu: 'Dancing Girls',
dlugosc_trwania: 3.46
},
{
numer: 2,
tytul_utworu: 'Wouldn’t It Be Good',
dlugosc_trwania: 4.32
},
{
numer: 3,
tytul_utworu: 'Drum Talk',
dlugosc_trwania: 3.10
},
{
numer: 4,
tytul_utworu: 'Bogart',
dlugosc_trwania: 4.38
}
]
}
})
Thanks to Faizul Hassan for your help and Yours, if you help me <3
Copy pasting the solution here from our conversation in another post..
Here is the soultion for your second question.
Lets see an example using unwind:
Without divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: "$album.utwor.dlugosc_trwania" }
}
},
]);
With divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: { $divide: ["$album.utwor.dlugosc_trwania", 60] } }
}
},
]);
I have a document like this:
_id:'someId',
sales:
[
{
_id:'111',
alias:'xxx',
amount:500,
name: Apple, //items with same alias always have same name and quantity
quantity:2
},
{
_id:'222',
alias:'abc',
amount:100,
name: Orange,
quantity:14
},
{
_id:'333',
alias:'xxx',
amount:300,
name: Apple, //items with same alias always have same name and quantity
quantity:2
}
]
The alias field is here to 'group' items/documents whenever they appear to have same alias i.e to be 'embeded' as one with the amount summed up.
I need to display some sort of a report in such a way that those elements which have same alias they should be displayed as ONE and the others which doesn't share same alias to remain as they are.
Example, For the sample document above, I need an output like this
[
{
alias:'xxx',
amount:800
},
{
alias:'abc',
amount:100
}
]
WHAT I HAVE TRIED
MyShop.aggregate([
{$group:{
_id: "$_id",
sales:{$last :"$sales"}
},
{$project:{
"sales.amount":1
}}
}
])
This just displays as a 'list' regardless of the alias. How do I achieve summing up amount based on the alias?
You can achieve this using $group
db.collection.aggregate([
{
$unwind: "$sales"
},
{
$group: {
_id: {
_id: "$_id",
alias: "$sales.alias"
},
sales: {
$first: "$sales"
},
_idsInvolved: {
$push: "$sales._id"
},
amount: {
$sum: "$sales.amount"
}
}
},
{
$group: {
_id: "$_id._id",
sales: {
$push: {
$mergeObjects: [
"$sales",
{
alias: "$_id.alias",
amount: "$amount",
_idsInvolved: "$_idsInvolved"
}
]
}
}
}
}
])
Mongo Playground
You can use below aggregation
db.collection.aggregate([
{
"$addFields": {
"sales": {
"$map": {
"input": {
"$setUnion": [
"$sales.alias"
]
},
"as": "m",
"in": {
"$let": {
"vars": {
"a": {
"$filter": {
"input": "$sales",
"as": "d",
"cond": {
"$eq": [
"$$d.alias",
"$$m"
]
}
}
}
},
"in": {
"amount": {
"$sum": "$$a.amount"
},
"alias": "$$m",
"_idsInvolved": "$$a._id"
}
}
}
}
}
}
}
])
MongoPlayground
I have following structure in users collection:
[
{ "name": "Ivan",
"payments": [
{"date": new Date("2019-01-01"), "details": [{"payment_system": "A", "spent": 95},
{"payment_system": "B", "spent": 123}]},
{"date": new Date("2019-01-03"), "details": [{"payment_system": "A", "spent": 12},
{"payment_system": "B", "spent": 11}]}]},
{ "name": "Mark",
"payments": [
{"date": new Date("2019-01-01"), "details": [{"payment_system": "D", "spent": 456},
{"payment_system": "B", "spent": 123}]},
{"date": new Date("2019-01-02"), "details": [{"payment_system": "A", "spent": 98},
{"payment_system": "C", "spent": 4}]}]}
]
Is it any way to add a field to users who spent more than, lets say 100 during the specific date range in specific payment system?
I tried updateMany, but have no idea how to filter "details" array element based on payment_system field.
For payment_system IN ("A", "C"), date >= "2019-01-02", spent_total >= 100 update should return
[
{ "name": "Ivan", ...},
{ "name": "Mark", "filter_passed": true, ... }
]
This this one:
db.collection.aggregate([
{
$set: {
payments: {
$filter: {
input: "$payments",
cond: { $gte: ["$$this.date", new Date("2019-01-02")] }
}
}
}
},
{
$set: {
spent_total: {
$reduce: {
input: "$payments.details.spent",
initialValue: [],
in: { $concatArrays: ["$$value", "$$this"] }
}
}
}
},
{ $set: { spent_total: { $sum: "$spent_total" } } },
{ $match: { "spent_total": { $gte: 100 } } }
])
Mongo Playground
Update:
Filter by payment_system is a bit longer. You have to $unwind and $group:
db.collection.aggregate([
{
$set: {
payments: {
$filter: {
input: "$payments",
cond: { $gte: ["$$this.date", new Date("2019-01-02")] }
}
}
}
},
{ $unwind: "$payments" },
{
$set: {
"payments.details": {
$filter: {
input: "$payments.details",
cond: { $in: ["$$this.payment_system", ["A", "C"]] }
},
},
}
},
{
$group: {
_id: { _id: "$_id", name: "$name", },
payments: { $push: "$payments" }
}
},
{
$set: {
spent_total: {
$reduce: {
input: "$payments.details.spent",
initialValue: [],
in: { $concatArrays: ["$$value", "$$this"] }
}
}
}
},
{ $set: { spent_total: { $sum: "$spent_total" } } },
{ $match: { "spent_total": { $gte: 100 } } },
{ // just some cosmetic
$project: {
_id: "$_id._id",
name: "$_id.name",
payments: 1
}
}
])
You cannot update your collection like db.collection.updateMany({}, [<the aggregation pipeline from above>]) because it contains $unwind and $group.
However, you can make $lookup or $out to save entire result into new collection.
If you need to sum up for each payment_system individually then try:
db.collection.aggregate([
{
$set: {
payments: {
$filter: {
input: "$payments",
cond: { $gte: ["$$this.date", new Date("2019-01-01")] }
}
}
}
},
{ $unwind: "$payments" },
{
$set: {
"payments.details": {
$filter: {
input: "$payments.details",
cond: { $in: ["$$this.payment_system", ["A", "B","C"]] }
},
},
}
},
{ $unwind: "$payments.details" },
{
$group: {
_id: {
_id: "$_id",
name: "$name",
payments: "$payments.details.payment_system"
},
spent_total: { $sum: "$payments.details.spent" }
}
},
{ $match: { "spent_total": { $gte: 100 } } },
{
$project: {
_id: "$_id._id",
name: "$_id.name",
payments: "$_id.payments",
spent_total: 1
}
}
])