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!
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.
I have these collections:
users
{
_id: "userId1",
// ...
tracks: ["trackId1", "trackId2"],
};
tracks
{
_id: "trackId1",
// ...
creatorId: "userId1",
categoryId: "categoryId1"
}
categories
{
_id: "categoryId1",
// ...
tracks: ["trackId1", "trackId15", "trackId20"],
};
by using the following code, I am able to get a track by its ID and add the creator
tracks.aggregate([
{
$match: { _id: ObjectId(trackId) },
},
{
$lookup: {
let: { userId: { $toObjectId: "$creatorId" } },
from: "users",
pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$userId"] } } }],
as: "creator",
},
},
{ $limit: 1 },
])
.toArray();
Response:
"track": {
"_id": "trackId1",
// ...
"categoryId": "categoryId1",
"creatorId": "userId1",
"creator": {
"_id": "userId1",
// ...
"tracks": [
"trackId5",
"trackId10",
"trackId65"
]
}
}
but what I am struggling with is that I want the creator.tracks to aggregate also returning the tracks by their ID (e.g up to last 5), and also to get the last 5 tracks from the categoryId
expected result:
"track": {
"_id": "trackId1",
// ...
"categoryId": "categoryId1",
"creatorId": "userId1",
"creator": {
"_id": "userId1",
"tracks": [
{
"_id": "trackId5",
// the rest object without the creator
},
{
"_id": "trackId10",
// the rest object without the creator
},
{
"_id": "trackId65",
// the rest object without the creator
},
]
},
// without trackId1 which is the one that is being viewed
"relatedTracks": [
{
"_id": "trackId15",
// the rest object without the creator
},
{
"_id": "trackId20",
// the rest object without the creator
},
]
}
I would appreciate any explanation/help to understand what is the best one to do it and still keep the good performance
Query
start from a track
join with users using the trackId get all the tracks of the creator
(creator-tracks)
join with categories using the categoryId to get all the tracks of the category (related tracks)
remove from related-tracks the tracks of the creator
take the last 5 from both using $slice (creator-tracks and related-tracks)
*i added 2 extra lookups to get all info of the tracks, its empty arrays because i dont have enough data(i have only trackId1), with all the data it will work
PlayMongo
db.tracks.aggregate([
{
"$match": {
"_id": "trackId1"
}
},
{
"$lookup": {
"from": "users",
"localField": "creatorId",
"foreignField": "_id",
"as": "creator-tracks"
}
},
{
"$set": {
"creator-tracks": {
"$arrayElemAt": [
"$creator-tracks.tracks",
0
]
}
}
},
{
"$lookup": {
"from": "categories",
"localField": "categoryId",
"foreignField": "_id",
"as": "related-tracks"
}
},
{
"$set": {
"related-tracks": {
"$arrayElemAt": [
"$related-tracks.tracks",
0
]
}
}
},
{
"$set": {
"related-tracks": {
"$filter": {
"input": "$related-tracks",
"cond": {
"$not": [
{
"$in": [
"$$this",
"$creator-tracks"
]
}
]
}
}
}
}
},
{
"$set": {
"creator-tracks": {
"$slice": [
{
"$filter": {
"input": "$creator-tracks",
"cond": {
"$ne": [
"$$this",
"$_id"
]
}
}
},
-5
]
}
}
},
{
"$set": {
"related-tracks": {
"$slice": [
"$related-tracks",
-5
]
}
}
},
{
"$lookup": {
"from": "tracks",
"localField": "creator-tracks",
"foreignField": "_id",
"as": "creator-tracks-all-info"
}
},
{
"$lookup": {
"from": "tracks",
"localField": "related-tracks",
"foreignField": "_id",
"as": "related-tracks-all-info"
}
}
])
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 want to join two collections and find the documents where has one equal field and one unequal field!
This is what I was tried, But not work
db.collectionOne.aggregate[
{
"$match": {
"$and": [
{ "$text": { "$search": "this is my query" } },
{ "b": { "$eq": "60e849054d2f0d409041b6a2" } }
]
}
},
{ "$addFields": { "pID": { "$toString": "$_id" }, "score": { "$meta": "textScore" } } },
{
"$lookup": {
"from": "collectionsTwo",
"as": "collectionsTwoName",
"pipeline": [{
"$match": {
"$expr": {
"$and": [{
"$ne": ["$fieldOne", "60dd0f98d10f072e2a225502"] // This one is unqual field
}, { "$eq": ["$pID", "$fieldTwo"] }] // This one is equal field
}
}
}]
}
},
{ "$sort": { "score": -1 } },
{ "$limit": 1 }
])
Fields in the source document, i.e. $pID are not available inside the lookup pipeline.
In order to reference those values, you would need to define a variable using let, such as:
{
"$lookup": {
"from": "collectionsTwo",
"as": "collectionsTwoName",
"let": { "srcpID":"$pID" },
"pipeline": [{
"$match": {
"$expr": {
"$and": [{
"$ne": ["$fieldOne", "60dd0f98d10f072e2a225502"] // This one is unqual field
}, { "$eq": ["$$srcpID", "$fieldTwo"] }] // This one is equal field
}
}
}]
}
},
See https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries