MongoDB aggregation - how to add field to every document in array? - javascript

Let's say that I have two collections in my database: actor and movie.
Each movie has a title and I want to add this title field to every document in the actors array. I get the actors array by using aggregation pipeline together with $lookup operation.
My attempt was to $map on the actors array and add title field. But it doesn't work, unfortunately:
db.movie
.aggregate([
{
$lookup: {
from: 'actor',
localField: 'id',
foreignField: 'movie',
as: 'actors',
},
},
{
$project: {
actors: {
$map: {
input: '$actors',
as: 'actor',
in: { $set: { title: '$title' } }, // this line doesn't work
},
},
},
},
])

Related

Add count inside MongoDB aggregation [duplicate]

I'm using such aggregation to sort all products by deep nested field ObjectId.
At first I populate catalogProduct field.
Then populate category inside catalogProduct.
Sort all data by category Id (return product if ids arr includes category._id)
Sort in reverse order, returns page and limit by 8 for pagination.
Then getting total count of all sorted products without paginatin and limit.
const sortedProducts = await StorageModel.aggregate([
// Unite products arr and totalCount of sorted products
{$facet: {
"sortedProducts": [
// populate catalogProduct ref by Id
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct" },
// populate category ref by Id inside catalogProduct object
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct.category" },
// returns product, if ids arr includes a catalogProduct.category._id
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// sort in reverse order
{ $sort: { _id: -1 } },
// returns only *page
{ $skip: (page - 1) * 8 },
/// limit result by 8
{ $limit: 8 },
],
// total count for pagination, the same operations
"totalCount": [
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
{ $unwind: "$catalogProduct" },
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
{ $unwind: "$catalogProduct.category" },
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// get total count of sorted data, without limit and pagination
{$count : "totalCount"},
]
}},
]);
products = sortedProducts[0].sortedProducts
totalProducts = sortedProducts[0].totalCount.totalCount
I'm getting such data:
[
{ sortedProducts: [ [Object], [Object] ], totalCount: [ [Object] ] }
]
And It's fine. But I think, that aggregation can be simplified, and i don't need to repeat operations to get total count, but I don't know how.
You can observe the starting stages until $match by catalogProduct.category._id is repeated in the 2 $facet. Therefore, you can simply factor them out, then put the afterwards stages into $facet respectively.
Below is my suggested version of your code:
StorageModel.aggregate([
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct" },
// populate category ref by Id inside catalogProduct object
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct.category" },
// returns product, if ids arr includes a catalogProduct.category._id
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// Unite products arr and totalCount of sorted products
{$facet: {
"sortedProducts": [
// populate catalogProduct ref by Id
// sort in reverse order
{ $sort: { _id: -1 } },
// returns only *page
{ $skip: (page - 1) * 8 },
/// limit result by 8
{ $limit: 8 },
],
// total count for pagination, the same operations
"totalCount": [
// get total count of sorted data, without limit and pagination
{$count : "totalCount"},
]
}},
]);

$addFields $size property always returns zero - mongoose

Hi I have added $addFields property in aggregate query and $size of my documents always return 0. here are my tables and query
table post:
{
_id: 1,
text: 'some text',
}
table comments:
{
_id: 1,
text: 'comment text',
postId: 1
}
In aggregate i have the following
let aggregateDataQuery = [
{
$lookup: {
from: 'comments',
localField: '_id',
foreignField: 'postId',
as: 'numberOfComments',
},
},
{
$addFields: {numberOfComments: { $size: { $ifNull: ['$numberOfComments', []] } }},
},
];
This query always result in numberOfComments: 0. I am sure that there are comments against postId 1 but result is always zero. Any Idea what i'm missing here. thanks
Hi the clause from in $lookup stay of the collection than you want to join.
The starter collection must be posts collection.
So you should put comments instead posts.
The $size attribute not work because the join not join.
The code must be shomething like:
let aggregateDataQuery = [
{
$lookup: {
from: 'comments',
localField: '_id',
foreignField: 'postId',
as: 'numberOfComments',
},
},
{
$addFields: {numberOfComments: { $size: { $ifNull: ['$numberOfComments', []] } }},
},
];
Posts.aggregate(aggregateDataQuery);

Mongoose date $gte operator not working as expected

I am trying to write query for last week but it is not working as expected in mongoDB.
[{
$lookup: {
from: 'reviews',
localField: 'groupReviews',
foreignField: '_id',
as: 'groupReviews'
}
}, {
$match: {
$and: [{
_id: {
$eq: ObjectId('5f247eea8ad8eb53883f4a9b')
}
},
{
"groupReviews.reviewCreated": {
$gte: ISODate('2020-06-20T10:24:51.303Z')
}
}
]
}
}, {
$project: {
count: {
$size: "$groupReviews",
},
groupReviews: {
$slice: ["$groupReviews", 0, 20],
}
}
}, {
$sort: {
"groupReviews.reviewCreated": -1
}
}]
the actual result: above code returning results which is older than 2020-06-20.
the expected result: it should not display older than 2020-06-20.
I am attaching an image for more reference.
Image Link
The $match stages matches entire documents, not individual array elements. If the array contains at least one element that satisfies the $gte condition, the document will be matched and passed along the pipeline.
If you want to remove the individual array elements that are older than the given date, you could either
$unwind the array before matching and $group to rebuild it with only the matching entries
Use $filter in your $project stage to eliminate the unwanted elements prior to slicing

MongoDB: Project only the items that was queried for in the array?

I have a user document, each user has an array of objects
Given an array of item tags, I need to find the user whose item array has the item-tag, and return the entire user object except the items array, in which I only want to return the first item tags that existed in the tagArray that was used for the intial query.
//user document
{
user: 'John',
items: [ObjectId('ABC'), ObjectId('123') ...]
}
//item document
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
},
{
_id: ObjectId('DEF'),
tag: 'some-unique-tag'
}
Users have a 1-to-N relationship with items, the items may repeat within the User's items array.
This is what I current have, which returns the entire user object, but also all the items within the array.
const tagArray = [ 'some-unique-id', 'some-unique-tag']
items.aggregate([
{ $match: { 'tag': { $in: tagArray } }},
{ $lookup: {
from: "users",
localField: "tag",
foreignField: '_id',
as: 'userInfo'
}
},
{
$project: {??} //<--- I'm pretty sure I'm missing something in the project
])
Outcome that I have now:
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
userInfo : [ {user: 'John', items: [ObjectId('ABC'), ObjectId('123') ...] }]
}
What I want to achieve:
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
userInfo : [ {user: 'John', items: [ObjectId('ABC')]} ]
}
Edit:
There is a similar question here : Retrieve only the queried element in an object array in MongoDB collection
However in my case, I need the filter condition to be "one of the the tags that is in the tagArray.
Any suggestion or pointers would be appreciated, thank you!
I don't know if I understood well what you need, but I think this is a good start (maybe you can modify it by yourself):
Test data:
// users collection
[
{
user: "John",
items: [
ObjectId("5a934e000102030405000002"),
ObjectId("5a934e000102030405000003")
]
}
]
// items collection
[
{
_id: ObjectId("5a934e000102030405000002"),
tag: "some-unique-id"
},
{
_id: ObjectId("5a934e000102030405000009"),
tag: "some-unique-tag"
}
]
}
Query:
db.users.aggregate([
{
$lookup: {
from: "items",
localField: "items",
foreignField: "_id",
as: "userInfo"
}
},
// create new fields inside the userInfo array
{
$project: {
"userInfo.user": "$user",
"userInfo.items": "$items",
"tag": {
$arrayElemAt: ["$userInfo.tag", 0]
}
}
},
// filter the userInfo.items field, based on _id field
// it's important to use $arrayElemAt here
{
$addFields: {
"userInfo.items": {
$filter: {
input: {
$arrayElemAt: [
"$userInfo.items",
0
]
},
as: "i",
cond: {
$in: [
"$$i",
[
"$_id"
]
]
}
}
}
}
}
])
Result:
[
{
"_id": ObjectId("5a934e000102030405000002"),
"tag": "some-unique-id",
"userInfo": [
{
"items": [
ObjectId("5a934e000102030405000002")
],
"user": "John"
}
]
}
]

using max query with mongoose

I am new with mongoose and still trying to understand how make correct queries
I have 2 simple Models
User :
const UserSchema = new Schema({
name: String,
age: Number,
movies:[{
type: Schema.Types.ObjectId,
ref: 'movie'
}]
}, { collection: 'USER_COLLEC' });
Movie :
const MovieSchema = new Schema({
title:String ,
duration: Number
}, { collection: 'MOVIE_COLLEC' });
What I want is the user with le longest movie ( highest duration )
For now I got that :
db.getCollection('USER_COLLEC') .
aggregate([
{ "$unwind": "$movies" } ,
{ $lookup:
{from: "MOVIE_COLLEC",
localField: "movies",
foreignField: "_id",
as: "movieContent"},
} ,
{ $unwind: "$movieContent" },
{ $group:
{ maxDuration: { $max: "$movieContent.duration" },
}
}
])
But it will only find the max duration with no user attached to it...
And indeed I only ask for the max duration on my query, but after the lookup I lose my user :(
How can I can keep it, or retrieve my user data ?
If you have any idea, I am completely stuck...
Thanks guys !
you can use $push to get the movie object as well.
db.getCollection('USER_COLLEC') .
aggregate([
{ "$unwind": "$movies" } ,
{ $lookup:
{from: "MOVIE_COLLEC",
localField: "movies",
foreignField: "_id",
as: "movieContent"},
} ,
{ $unwind: "$movieContent" },
{ $group:
{ _id: { $max: "$movieContent.duration" },
"movie": {
"$push": "movieContent"
}
}
}
])
After this, just get search for the Movie's _id in the user's movies array
UserSchema.find({movies:{$in:movieContent[0]._id}});
OR, instead of $push you can also use $first
{ $first: "$movieContent" }
Then you won't get it in an array.
Update:
Instead of {$push: $movieContent} or{$first: $movieContent}, you could just push $$ROOT:
{$push: $$ROOT} or {$first: $$ROOT}
and then you'll get the entire object. You don't need to fire another query to get the user.
I finally managed to find the solution, the $group was not the solution
db.getCollection('USER_COLLEC') .
aggregate([
{ "$unwind": "$movies" } ,
{ $lookup:
{from: "MOVIE_COLLEC",
localField: "movies",
foreignField: "_id",
as: "movieContent"},
} ,
{ $unwind: "$movieContent" },
{$sort: {"movieContent.duration":-1}},
{ $project: { "user":"$name","duration" : "$movieContent.duration"} } ,
{ $limit : 1 }
Which gives me something like :
{
"_id" : ObjectId("59d2f64dded1c008192f7e73"),
"user" : "Michael",
"duration" : 96
}

Categories

Resources