I have collection with documents, for example:
[
{
'id':'1'
'some_field':'test',
'rates':[
{'user_id':'12','rate':'very_good'},
{'user_id':'13','rate':'very_good'}
{'user_id':'14','rate':'bad'},
{'user_id':'15','rate':'normal'}
]
}
]
And i have collection with values of rates in string:
[
{
"rate_name" : "bad",
"rate_value" : 1
},
{
"rate_name" : "normal",
"rate_value" : 2
},
{
"rate_name" : "very_good",
"rate_value" : 3
},
]
I need map data from first collection from array rates with value from second collection and group this values to new field.
For example:
[
{
'id':'1'
'some_field':'test',
'rates':[3,3,1,2]
]
}
]
How i can do this?
Here's one way you could do it if "$getField" is available (MongoDB server version 5.0 and above). See below for another option.
db.ratings.aggregate([
{
"$lookup": {
"from": "ratingScale",
"as": "ratingScale",
"pipeline": []
}
},
{
"$set": {
"rates": {
"$map": {
"input": "$rates",
"as": "rate",
"in": {
"$getField": {
"field": "rate_value",
"input": {
"$first": {
"$filter": {
"input": "$ratingScale",
"as": "nameValue",
"cond": {"$eq": ["$$rate.rate", "$$nameValue.rate_name"]}
}
}
}
}
}
}
}
}
},
{"$unset": "ratingScale"}
])
Try it on mongoplayground.net.
If "$getField" is unavailable, here's another way you could do it.
db.ratings.aggregate([
{
"$lookup": {
"from": "ratingScale",
"as": "ratingScale",
"pipeline": []
}
},
{
"$set": {
"rates": {
"$map": {
"input": "$rates",
"as": "rate",
"in": {
"$first": {
"$filter": {
"input": "$ratingScale",
"as": "nameValue",
"cond": {"$eq": ["$$rate.rate", "$$nameValue.rate_name"]}
}
}
}
}
}
}
},
{
"$set": {
"rates": {
"$map": {
"input": "$rates",
"as": "rate",
"in": "$$rate.rate_value"
}
}
}
},
{"$unset": "ratingScale"}
])
Try it on mongoplayground.net.
You can do a plain $unwind then $lookup approach.
db.col1.aggregate([
{
$unwind: "$rates"
},
{
"$lookup": {
"from": "col2",
"localField": "rates.rate",
"foreignField": "rate_name",
"as": "rates"
}
},
{
"$unwind": "$rates"
},
{
$group: {
_id: "$_id",
some_field: {
$first: "$some_field"
},
rates: {
$push: "$rates.rate_value"
}
}
}
])
Mongo Playground
Related
How can i aggregate two collections if i have the index in multidimensional array?
//collection 1 item example:
{
_id: ObjectId('000'),
trips: [ // first array
{ // trip 1
details: [ // array in first array
{ // detail 1
key: ObjectId('111') // ==> _id i want aggregate
}
]
}
]
}
//collection 2 item exampe:
{
_id: ObjectId('111'),
description: 'any info here...'
}
what is the procedure to do in order to obtain a valid result?
I don't want to reprocess every data after doing a "find".
This is the result I would like to get
//collection 1 after aggregate:
{
_id: ObjectId('000'),
trips: [
{ // trip 1
details: [
{ // detail 1
key: ObjectId('111') // id i want aggregate
_description_obj: {
_id: ObjectId('111'),
description: 'any info here...'
}
}
]
}
]
}
Here's one way you could do it.
db.coll1.aggregate([
{
"$match": {
_id: ObjectId("000000000000000000000000")
}
},
{
"$lookup": {
"from": "coll2",
"localField": "trips.details.key",
"foreignField": "_id",
"as": "coll2"
}
},
{
"$set": {
"trips": {
"$map": {
"input": "$trips",
"as": "trip",
"in": {
"$mergeObjects": [
"$$trip",
{
"details": {
"$map": {
"input": "$$trip.details",
"as": "detail",
"in": {
"$mergeObjects": [
"$$detail",
{
"_description_obj": {
"$first": {
"$filter": {
"input": "$coll2",
"cond": {"$eq": ["$$this._id", "$$detail.key"]}
}
}
}
}
]
}
}
}
}
]
}
}
},
"coll2": "$$REMOVE"
}
}
])
Try it on mongoplayground.net.
I'm trying to get aggregations with same aggregation pipeline including $match and $group operations from multiple collections.
For example,
with a users collection and collections of questions, answers and comments where every document has authorId and created_at field,
db = [
'users': [{ _id: 123 }, { _id: 456} ],
'questions': [
{ authorId: ObjectId('123'), createdAt: ISODate('2022-09-01T00:00:00Z') },
{ authorId: ObjectId('456'), createdAt: ISODate('2022-09-05T00:00:00Z') },
],
'answers': [
{ authorId: ObjectId('123'), createdAt: ISODate('2022-09-05T08:00:00Z') },
{ authorId: ObjectId('456'), createdAt: ISODate('2022-09-01T08:00:00Z') },
],
'comments': [
{ authorId: ObjectId('123'), createdAt: ISODate('2022-09-01T16:00:00Z') },
{ authorId: ObjectId('456'), createdAt: ISODate('2022-09-05T16:00:00Z') },
],
]
I want to get counts of documents from each collections with created_at between a given range and grouped by authorId.
A desired aggregation result may look like below. The _ids here are ObjectIds of documents in users collection.
\\ match: { createdAt: { $gt: ISODate('2022-09-03T00:00:00Z) } }
[
{ _id: ObjectId('123'), questionCount: 0, answerCount: 1, commentCount: 0 },
{ _id: ObjectId('456'), questionCount: 1, answerCount: 0, commentCount: 1 }
]
Currently, I am running aggregation below for each collection, combining the results in the backend service. (I am using Spring Data MongoDB Reactive.) This seems very inefficient.
db.collection.aggregate([
{ $match: {
created_at: { $gt: ISODate('2022-09-03T00:00:00Z') }
}},
{ $group : {
_id: '$authorId',
count: {$sum: 1}
}}
])
How can I get the desired result with one aggregation?
I thought $unionWith or $lookup may help but I'm stuck here.
You can try something like this, using $lookup, here we join users, with all the three collections one-by-one, and then calculate the count:
db.users.aggregate([
{
"$lookup": {
"from": "questions",
"let": {
id: "$_id"
},
"pipeline": [
{
"$match": {
$expr: {
"$and": [
{
"$gt": [
"$createdAt",
ISODate("2022-09-03T00:00:00Z")
]
},
{
"$eq": [
"$$id",
"$authorId"
]
}
]
}
}
}
],
"as": "questions"
}
},
{
"$lookup": {
"from": "answers",
"let": {
id: "$_id"
},
"pipeline": [
{
"$match": {
$expr: {
"$and": [
{
"$gt": [
"$createdAt",
ISODate("2022-09-03T00:00:00Z")
]
},
{
"$eq": [
"$$id",
"$authorId"
]
}
]
}
}
}
],
"as": "answers"
}
},
{
"$lookup": {
"from": "comments",
"let": {
id: "$_id"
},
"pipeline": [
{
"$match": {
$expr: {
"$and": [
{
"$gt": [
"$createdAt",
ISODate("2022-09-03T00:00:00Z")
]
},
{
"$eq": [
"$$id",
"$authorId"
]
}
]
}
}
}
],
"as": "comments"
}
},
{
"$project": {
"questionCount": {
"$size": "$questions"
},
"answersCount": {
"$size": "$answers"
},
"commentsCount": {
"$size": "$comments"
}
}
}
])
Playground link. In the above query, we use pipelined form of $lookup, to perform join on some custom logic. Learn more about $lookup here.
Another way is this, perform normal lookup and then filter out the elements:
db.users.aggregate([
{
"$lookup": {
"from": "questions",
"localField": "_id",
"foreignField": "authorId",
"as": "questions"
}
},
{
"$lookup": {
"from": "answers",
"localField": "_id",
"foreignField": "authorId",
"as": "answers"
}
},
{
"$lookup": {
"from": "comments",
"localField": "_id",
"foreignField": "authorId",
"as": "comments"
}
},
{
"$project": {
questionCount: {
"$size": {
"$filter": {
"input": "$questions",
"as": "item",
"cond": {
"$gt": [
"$$item.createdAt",
ISODate("2022-09-03T00:00:00Z")
]
}
}
}
},
answerCount: {
"$size": {
"$filter": {
"input": "$answers",
"as": "item",
"cond": {
"$gt": [
"$$item.createdAt",
ISODate("2022-09-03T00:00:00Z")
]
}
}
}
},
commentsCount: {
"$size": {
"$filter": {
"input": "$comments",
"as": "item",
"cond": {
"$gt": [
"$$item.createdAt",
ISODate("2022-09-03T00:00:00Z")
]
}
}
}
}
}
}
])
Playground link.
How to filter the product and optimize mongodb query,
We would like to get popular products base on some conditions i.e which products are orders, view and likes.
db.products.aggregate([
{
"$lookup": {
"from": "orders",
"localField": "_id",
"foreignField": "product_id",
"as": "orders"
}
},
{
"$addFields": {
"orderCount": {
"$size": {
"$cond": [
{
"$isArray": "$orders"
},
"$orders",
[]
]
}
}
}
},
{
"$addFields": {
"likeCount": {
"$size": {
"$cond": [
{
"$isArray": "$likes"
},
"$likes",
[]
]
}
}
}
},
{
"$addFields": {
"sumCount": {
"$sum": [
"$orderCount",
"$likeCount",
"$view"
]
}
}
},
{
$sort: {
"sumCount": -1
}
}
])
https://mongoplayground.net/p/fIG3-yHGuV6
Have to use multiple $addFields what would be best option to achieve the products that have the most orders, likes and views. please guide
thanks
I would suggest 2 corrections,
orders size does not need verification if it is an array or not condition because $lookup stage will always return in array
You can do both operations for orderCount and likeCount in a single $addFields stage
You final query would be,
db.products.aggregate([
{
"$lookup": {
"from": "orders",
"localField": "_id",
"foreignField": "product_id",
"as": "orders"
}
},
{
"$addFields": {
"orderCount": { "$size": "$orders" },
"likeCount": {
"$size": {
"$cond": [{ "$isArray": "$likes" }, "$likes", []]
}
}
}
},
{
"$addFields": {
"sumCount": {
"$sum": ["$orderCount", "$likeCount", "$view"]
}
}
},
{ "$sort": { "sumCount": -1 } }
])
Playground
You can also use projection to minimize the code
{
"$project": {
"likes": 1,
"orderCount": {
"$size": {
"$cond": {
"if": {
"$isArray": [
"$orders"
]
},
"then": "$orders",
"else": []
}
}
},
"likeCount": {
"$size": {
"$cond": {
"if": {
"$isArray": [
"$likes"
]
},
"then": "$likes",
"else": []
}
}
},
"views": {
"$ifNull": [
"$view",
0
]
}
}
},
https://mongoplayground.net/p/qUNftLP_-PN
check the mongoplayground.
I'm having the following query
db.getCollection('matches').aggregate([{
"$lookup": {
"from": "player",
"localField": "players.account_id",
"foreignField": "account_id",
"as": "players2"
}
}, {
"$addFields": {
"players": {
"$map": {
"input": "$players",
"in": {
"$mergeObjects": [
"$$this", {
"$arrayElemAt": [
"$players2", {
"$indexOfArray": [
"$players.account_id",
"$$this.account_id"
]
}
]
}
]
}
}
}
}
}, {
"$set": {
"players.match_id": "$match_id",
"players.radiant_win": "$radiant_win"
}
}, {
"$unwind": "$players"
}, {
"$replaceRoot": {
"newRoot": "$players"
}
}, {
"$project": {
"_id": 1,
"match_id": 1,
"account_id": 1,
"hero_id": 1,
"radiant_win": 1
}
}
])
which is supposed to match an inner array with another collection, merge the objects in the arrays by the matching and then unwrap ($unwind) the array into a new collection.
Unfortunately, I'm getting duplicate Object ids which is sort of a problem for when I want to export this collection.
How can I ensure unique Object_Ids for the aggregation?
Thanks in advance!
I have a MongoDB collection with documents of the following structure (non-interesting bits left out):
{
displayFieldId: "abcd",
fields: [
{
fieldId: "efgh",
value: "cake"
},
{
fieldId: "abcd",
value: "cheese"
},
....
],
....
}
I would like to run a query on this collection to fetch only the element in the fields array which fieldId matches the document's displayFieldId. The result of the query on the document above should thus be:
{
fields: [
{
fieldId: "abcd",
value: "cheese"
}
],
....
}
I constructed the following query. It does what I want, with the exception that the displayFieldValue is hard coded
db.containers.find({}, {
fields: {
$elemMatch: {
fieldId: "abcd"
}
}
});
Is there a way to make it look at the document's displayFieldId and use that value instead of the hard coded "abcd"?
The server is running MongoDB 3.2.6
If possible, I would like to do this without aggregation, but if that can't be done, then aggregation will have to do
With aggregation framework:
db.containers.aggregate([
{
"$redact": {
"$cond": [
{
"$anyElementTrue": [
{
"$map": {
"input": "$fields",
"as": "el",
"in": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{
"$project": {
"displayFieldId": 1,
"fields": {
"$filter": {
"input": "$fields",
"as": "el",
"cond": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
},
"otherfields": 1,
....
}
}
])
MongoDB 3.4:
db.containers.aggregate([
{
"$redact": {
"$cond": [
{
"$anyElementTrue": [
{
"$map": {
"input": "$fields",
"as": "el",
"in": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{
"$addFields": {
"fields": {
"$filter": {
"input": "$fields",
"as": "el",
"cond": {
"$eq": ["$$el.fieldId", "$displayFieldId"]
}
}
}
}
}
])
Without aggregation framework - using $where (the slow query):
db.containers.find({
"$where": function() {
var self = this;
return this.fields.filter(function(f){
return self.displayFieldId === f.fieldId;
}).length > 0;
}
}).map(function(doc){
var obj = doc;
obj.fields = obj.fields.filter(function(f){
return obj.displayFieldId === f.fieldId;
});
return obj;
})