mongodb aggregation with multidimensional array - mongodb

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.

Related

MongoDB nested aggregation

So, I have two collections User and Book, and I want to aggregate them to receive an output (shown at the end of the post) for a specific User ID.
below are the collections:
User
Each document contains User ID, Name of the User and an array containing ID of a document from Book collection and a Boolean property read.
[
{
_id: ObjectId('adfa2sd5'),
name: "Mia",
books: [
{
bid: "154854",
read: true
},
{
bid: "5475786",
read: false
}
]
},
{
_id: ObjectId('uai5as5a'),
name: "Jack",
books: [
{
bid: "5475786",
read: true
}
]
}
]
Book
Each document possesses a book ID and name of the book.
[
{
_id: ObjectId('154854'),
name: "The Great Gatsby"
},
{
_id: ObjectId('5475786'),
name: "Frankenstein"
},
]
Output:
The output contains the User ID, along an array book_list which contains detail of each book (id, name) from the documents of Book collection based on the books.bid from User document and read field which was along books.bid.
[
{
_id: ObjectId('adfa2sd5'),
book_list: [
{
_id: ObjectId('154854'),
name: "The Great Gatsby",
read: true
},
{
_id: ObjectId('5475786'),
name: "Frankenstein",
read: false
}
]
}
]
Here's one way you could do it.
db.users.aggregate([
{
"$match": {
"_id": ObjectId("fedcba9876543210fedcba98")
}
},
{
"$lookup": {
"from": "books",
"localField": "books.bid",
"foreignField": "_id",
"as": "bookList"
}
},
{
"$set": {
"books": {
"$map": {
"input": "$books",
"as": "book",
"in": {
"$mergeObjects": [
"$$book",
{
"name": {
"$getField": {
"field": "name",
"input": {
"$first": {
"$filter": {
"input": "$bookList",
"cond": {"$eq": ["$$this._id", "$$book.bid"]}
}
}
}
}
}
}
]
}
}
}
}
},
{"$unset": ["bookList", "name"]}
])
Try it on mongoplayground.net.

mongodb. how map strings to integers from another collection

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

MongoDB multiple/nested aggregations

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 generate object ids when $unwinding with aggregate in mongodb

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!

MongoDB: Select element from array based on another property in the document

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;
})