Mongoose aggregate Get count and append new value to the query result - javascript

Given that I have this two COLLECTIONS:.
1st.col
users:
{
_id :34,
name :"mama mia"
}
2nd.col
posts:
{
_id :67
body :" hello mongoose"
likes:[ 0: ObjectId("34") ]
}
I wanna get every posts with likes count.
And let suppose if I have a auth user id ready and a want to map through the likes and if the user._id(auth id) is found in the post likes i wanna append a new attribute to the collection result not in the db , i just want to modifies the result i'm gonna get.
To make more since of my question , this is the result expected :
[
{
_id : 67
body : " hello mongoose"
likesCount : 1
liked :true
likes :{
"34":{
name :"mama mia"
}
}
}
]

$lookup to join users collection
$map to iterate loop of likes array and return key-value format result
$arrayToObject to convert key-value array of object to object
$size to get total elements in likes array
$in to check current auth user id in likes array or not
let auth_user_id = ObjectId("34");
db.posts.aggregate([
{
$lookup: {
from: "users",
localField: "likes",
foreignField: "_id",
as: "likes"
}
},
{
$project: {
likes: {
$arrayToObject: {
$map: {
input: "$likes",
in: {
k: { $toString: "$$this._id" },
v: "$$this.name"
}
}
}
},
likesCount: { $size: "$likes" },
liked: { $in: [auth_user_id, "$likes"] },
body: 1
}
}
])
Playground

Related

return what was not found in array in MongoDB

say my database collection has
* user collection*
[
{id:'1'}
{id:'2'}
]
I have an array of object
[
{id:'1'}
{id:'2'}
{id:'3'}
]
I want the object that was not found in the collection.
I want
[
{id:'3'}
]
I'm currently have this
const records = await dbo
.collection('user collection')
.find({
'id': { $in: newArr },
})
.toArray();
I'm a bit stumped on what to do! ... hope someone can help Thanks!
Option 1:
Looks like this is what you need via the not in operation ( $nin ) when you need to check the not exisitng id in collection documents from provided array:
db.collection.aggregate([
{
$match: {
id: {
"$nin": [
1,
2
]
}
}
},
{
$group: {
_id: null,
"idnotIntheArray": {
$push: "$id"
}
}
}
])
Explained:
$match for any documents with id not in provided array.
$group all id's in an array
plaground1
Option 2:
And this is the option where you output only the array elements not existing in the collection:
db.collection.aggregate([
{
$group: {
_id: null,
ids: {
$push: "$id"
}
}
},
{
$project: {
missingFromCollection: {
"$setDifference": [
[
1,
5,
4
],
"$ids"
]
}
}
}
])
Explained:
Push all id elements from collection to array ids ( note this solution will not allow more then 16MB total size of id's )
Use $setDifference to identify the difference between the two arrays.
playground2
You can use this aggregation:
db.entity.aggregate([
{
$match : {
"myObjList.id" : 1
}
},
{
$unwind : "$myObjList"
},
{
$match : {
"myObjList.id" : 1
}
}
])
and my aggregation result:
{
"_id" : ObjectId("6225a0f78d435fd2845f1dd1"),
"myObjList" : {
"id" : 1
}
}

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"},
]
}},
]);

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"
}
]
}
]

How to remove duplicates in mongoDB with mongoose(NodeJS)

I have a collection in MongoDB where there are around (~200k records). My sample record would look like,
{
name:String,
slug:String,
metaDes:String,
content:String,
parentID:String,
thumbnail:String,
display:Boolean,
}
I am having a lot of duplicate records in the collection having same slug
I want to remove duplicate records based on slug
Is there any fast way to remove all duplicates with mongoose(Nodejs)?
Thanks!
Remove duplicate records in the collection having the same slug
db.table.aggregate([
{
"$group": {
_id: {slug: "$slug"},
slugs: { $addToSet: "$_id" } ,
count: { $sum : 1 }
}
},
{
"$match": {
count: { "$gt": 1 }
}
}
]).forEach(function(doc) {
doc.slugs.shift();
db.table.remove({
_id: {$in: doc.slugs}
});
})
Refarnce link

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