Get collection with changed one field - node.js

I have a problem with getting a collection array with changed one field in each document.
My document is:
{postId: "123123", author: "someId"}
And I want to search by author id in other collection and replace author with author name instead of id
Is there any simple way to do that?
EDIT:
I've wrote something like this
{
$lookup: {
as: "USER",
foreignField: "created",
from: "Users",
localField: "userName"
}
}
Then i recive array of documents from Users collections. I wan't only one based on the value from the field of collection posts
EDIT:
Sample documents from collections:
POSTS:
{
_id: 'p7c3d',
author: 'p9jhkl',
content: "Hello",
createdAt: '12-12-2019',
tags: ['hello'];
}
USERS:
{
_id: 'p9jhkl',
name: 'John Smith'
}
And I wan't that "John Smith" to be in post author field instead of user id.
Edit:
Picture of actual documents

Assuming there is only 1-to-1 relation i.e; documents in POSTS can have only one matching document in USERS.
POSTS :
/* 1 */
{
"_id" : "p7c3d",
"author" : "p9jhkl",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 2 */
{
"_id" : "p7c3d11",
"author" : "p9jhkl11",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 3 */
{
"_id" : "p7c3d1122",
"author" : "p9jhkl1122",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 4 */
{
"_id" : "p7c3d No",
"author" : "p9jhkl No",
"content" : "Hello",
"tags" : [
"hello"
]
}
USERS :
/* 1 */
{
"_id" : "p9jhkl",
"name" : "John Smith"
}
/* 2 */
{
"_id" : "p9jhkl11",
"name" : "John Smith11"
}
/* 3 */
{
"_id" : "p9jhkl1122",
"name" : "John Smith1122"
}
Please try this :
db.POSTS.aggregate([{
$lookup:
{
from: "USERS",
localField: "author",
foreignField: "_id",
as: "userDetails"
}
},{$addFields : {author: { $arrayElemAt: [ '$userDetails.name', 0 ] }}},
{$project : { userDetails: 0}}])
Result :
/* 1 */
{
"_id" : "p7c3d",
"author" : "John Smith",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 2 */
{
"_id" : "p7c3d11",
"author" : "John Smith11",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 3 */
{
"_id" : "p7c3d1122",
"author" : "John Smith1122",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 4 */
{
"_id" : "p7c3d No",
"content" : "Hello",
"tags" : [
"hello"
]
}
Above query would completely remove the field author from documents of POSTS collection where there is no match with USERS documents (this would help to quickly remove POSTS docs from final o/p which has no match with USERS using {$match : {author: {$exists : true}}}), Just in case you need all documents from POSTS as is irrespective of whether there is a match or not, Check document 4 in results for clarification, Please try this :
db.POSTS.aggregate([{
$lookup:
{
from: "USERS",
localField: "author",
foreignField: "_id",
as: "userDetails"
}
},{$addFields :
{author: {
$cond: { if: { $ne: [ "$userDetails", [] ] },
then : { $arrayElemAt: [ '$userDetails.name', 0 ] },
else : '$author'}}}}, {$project : { userDetails: 0}}])
Result :
/* 1 */
{
"_id" : "p7c3d",
"author" : "John Smith",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 2 */
{
"_id" : "p7c3d11",
"author" : "John Smith11",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 3 */
{
"_id" : "p7c3d1122",
"author" : "John Smith1122",
"content" : "Hello",
"tags" : [
"hello"
]
}
/* 4 */
{
"_id" : "p7c3d No",
"author" : "p9jhkl No",
"content" : "Hello",
"tags" : [
"hello"
]
}
With actual data :
db.POSTS.aggregate([{ $addFields: { "createdBy": { "$toObjectId": "$createdBy" } } }, {
$lookup:
{
from: "USERS",
localField: "createdBy",
foreignField: "_id",
as: "userDetails"
}
}, {
$addFields:
{
createdBy: {
$cond: {
if: { $ne: ["$userDetails", []] },
then: { $arrayElemAt: ['$userDetails.name', 0] },
else: { "$toString": "$createdBy" }
}
}
}
}, { $project: { userDetails: 0 } }])

This can be done in three stages:
Match the author in posts to only get posts for an author
Join to authors using the created field
Project the fields you want, and only include the author's name in the result.
Example Below:
db.getCollection("posts").aggregate(
[
{
$match: {
created: ObjectId("5dd446cf3ccb4305e7fbec96")
}
},
{
$lookup: {from: "authors",
let: { id: "$created" },
pipeline: [
{
$match: {
$expr:
{
$eq: ["$$id","$_id"] }
}
},
{ $project: { _id: 0, name: 1 } },
],
as: "author"
}
},
{
$project: {
author: {
$arrayElemAt: ["$author.name", 0]
}
}
},
]
);

Related

Getting null array as result in mongodb aggregate pipeline

Here is my code, I have two collections called cart and product, First I want to take the id of the product from the cart and then from the product collection get the product details
collection cart
{
"_id" : ObjectId("62f8086e29c549f34ab89df7"),
"user" : ObjectId("62f3f8600e93c17d1c25c2ed"),
"product" : [
ObjectId("62f391b9482a375c4f83de8e"),
ObjectId("62f39121482a375c4f83de8d"),
ObjectId("62f39200482a375c4f83de8f")
]
}
collection product
{
"_id" : ObjectId("62f39121482a375c4f83de8d"),
"name" : "iphone 15",
"category" : "mobiles",
"price" : "125",
"description" : "fastest iphone"
}
{
"_id" : ObjectId("62f391b9482a375c4f83de8e"),
"name" : "OnePlus Nord",
"category" : "mobile",
"price" : "40000",
"description" : "budget phone of OnePlus"
}
{
"_id" : ObjectId("62f39200482a375c4f83de8f"),
"name" : "Samsung M33",
"category" : "mobile",
"price" : "25000",
"description" : "mid range mobile"
}
Here is the function for getting the details
getcartProducts:(userId)=>{
return new Promise(async(resolve,reject)=>{
let cartItem=await db.get().collection(collection.CART_COLLECTION).aggregate([
{
"$match":{user:objectId(userId)}
},
{
"$lookup":{
"from":collection.PRODUCT_COLLECTIONS,
"let":{"prodList":'$product'},
pipeline:[
{
"$match":{ "$expr":{ "$in":["_id","$$prodList"],
},
},
},
],
"as":"cartItems"
}
}
]
).toArray()
resolve(cartItem)
})
}
Finally the function is called
router.get('/cart',verifyLogin,async (req,res)=>{
let products=await userHelpers.getcartProducts(req.session.user._id)
console.log(products)
res.render('user/cart')
})
The output is: Showing null array in cartItems: []
[
{
_id: new ObjectId("62f8086e29c549f34ab89df7"),
user: new ObjectId("62f3f8600e93c17d1c25c2ed"),
product: [
new ObjectId("62f391b9482a375c4f83de8e"),
new ObjectId("62f39121482a375c4f83de8d"),
new ObjectId("62f39200482a375c4f83de8f"),
new ObjectId("62f39121482a375c4f83de8d")
],
cartItems: []
}
]
Use $_id instead of _id in $in operator. Mongo Playground
// This is the raw query
db.carts.aggregate([
{
"$match": {
"user": ObjectId("62f3f8600e93c17d1c25c2ed")
}
},
{
"$lookup": {
"from": "products",
"as": "cartItems",
"let": {
"prodList": "$product"
},
pipeline: [
{
"$match": {
"$expr": {
"$in": [
"$_id", // note the change here, use $ for matching field in lookup collection
"$$prodList" // use $$ for matching variable
],
},
},
},
],
}
}
])
Change the following line in your example code:
"$match":{ "$expr":{ "$in":["_id","$$prodList"],
To:
"$match":{ "$expr":{ "$in":["$_id","$$prodList"],
This aggregation pipeline cane be simplified to: Mongo Playground
[
{
"$match": {
"user": ObjectId("62f3f8600e93c17d1c25c2ed")
}
},
{
"$lookup": {
"from": "products",
"as": "cartItems",
"localField": "product",
"foreignField": "_id"
}
}
]

how to get matched data after the lookup

I am trying to get values by matching location ids. But I am getting error.in this locationId is an array like locationId[id1,id2] and location is an id.
Mongodb:
[err, assetsList] = await to(Assets.aggregate([
{
$match: {
"company" : new mongoose.Types.ObjectId(company)
// "auditAssetAvailability" : false
}
},
{
$lookup: {
from: "audits",
localField: "location",
foreignField: "locationId",
as: "asset_locations"
}
},
{
$unwind: '$asset_locations'
},
{
$match: {
"location" : { $in: [new mongoose.Types.ObjectId("asset_locations")] }
}
},
{
$project: {
"asset_locations":1,
"asset_name":1, "asset_code":1
}
},
]))
error:
message: "Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
it was working if I pass values like below:
{
$match: {
"location" : { $in: [new mongoose.Types.ObjectId("5f0968286fbe3f1278fb299f"),new mongoose.Types.ObjectId("5f09b8086fbe3f1278fb29a8")] }
}
},
Example:
Audit record:
{
"_id" : ObjectId("5fb3dcec0a3b9b4b44fb77c7"),
"locationId" : [
ObjectId("5f0968286fbe3f1278fb299f"),
ObjectId("5f09b8086fbe3f1278fb29a8")
],
"assetTypeId" : [
ObjectId("5f09683a6fbe3f1278fb29a0")
],
"auditUserId" : [
ObjectId("5f0c1a96bde0191914faa169")
],
"status" : true,
"startDate" : ISODate("2020-11-03T00:00:00.000Z"),
"endDate" : ISODate("2020-11-30T00:00:00.000Z"),
"company" : ObjectId("5f0964314a8580273c5ce687"),
"auditName" : "Q1 Audit",
"auditStatus" : ObjectId("5fb36cec93e1df254c4b97c3"),
"createdAt" : ISODate("2020-11-17T14:23:40.325Z"),
"updatedAt" : ISODate("2020-11-17T14:25:36.524Z")
}
asset record:
{
"_id" : ObjectId("5fb3e0620a3b9b4b44fb77d5"),
"discarded_date" : null,
"discordedNote" : null,
"company" : ObjectId("5f0964314a8580273c5ce687"),
"location" : ObjectId("5f09b8916fbe3f1278fb29a9"),
"statusCondition" : "stock_type"
}
Thank you in Advance.

MongoDB : add New Field to existing sub document after $look stage or merge lookup response to main document

I want new field "isActive" inside modifierStatus sub document which is coming from modifieritems collection.
modifieritems collection :
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
"isActive" : 1
}
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
"isActive" : 0
}
favoritedrinks collection :
{
"alcoholName" : "whiskey",
"modifierList" : [{
"_id" : ObjectId("5e6a5a0e6d40624b12453a61"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
}
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a66"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
}]
}
my query is :
db.getCollection('favoritedrinks').aggregate([
{ "$sort": { "alcoholName": 1 } },
{"$lookup": {
"from": "modifieritems",
localField: 'modifierList.modifierId',
foreignField: '_id',
as: 'modifier'
}},
{
$project:{
"alcoholName" : "$alcoholName",
"modifierStatus":"$modifier",
}
},
]);
But my expected result :
{
"alcoholName" : "Whiskey",
"modifierStatus" : [
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a61"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
"isActive" : 1,
},
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a66"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
"isActive" : 0,
}
]
}
anyone please help me?
Try this query :
Update with new requirement :
db.favoritedrinks.aggregate([
{
"$sort": {
"alcoholName": 1
}
},
{
"$lookup": {
"from": "modifieritems",
localField: "modifierList.modifierId",
foreignField: "_id",
as: "modifierStatus"
}
},
{
$addFields: {
modifierStatus: {
$map: {
input: "$modifierList",
as: "m",
in: {
$mergeObjects: [
{
$arrayElemAt: [ /** As filter would only get one object (cause you'll have only one matching doc in modifieritems coll for each "modifierList.modifierId", So getting first element out of array, else you need to take this array into an object & merge that field to particular object of 'modifierList') */
{
$filter: {
input: "$modifierStatus",
cond: {
$eq: [
"$$this._id",
"$$m.modifierId"
]
}
}
},
0
]
},
"$$m"
]
}
}
}
}
},
{
$project: {
modifierStatus: 1,
alcoholName: 1,
_id: 0
}
}
])
Test : MongoDB-Playground
Old :
db.favoritedrinks.aggregate([
{
"$sort": {
"alcoholName": 1
}
},
{
$lookup: {
from: "modifieritems",
let: {
id: "$modifierList.modifierId"
},
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$id"] } }
},
/** Adding a new field modifierId(taken from _id field of modifieritems doc)
* to each matched document from modifieritems coll */
{
$addFields: {
modifierId: "$_id"
}
}
],
as: "modifierStatus"
}
},
/** By mentioning 0 to particular fields to remove them & retain rest all other fields */
{
$project: {
modifierList: 0,
_id: 0
}
}
])
Test : MongoDB-Playground
When you want $project to include a field's current value while keeping the same field name, you need only specify :1. When you use "$field" you are explicitly setting the value, which will overwrite any existing value.
Try making your projection:
{
$project:{
"alcoholName" : 1,
"modifier.isActive": 1,
"modifier.modifierName": 1
}
}

Mongoose: how to use aggregate and flatten subdocument to list

How can i get this result in this situation?
I have one collection named coins
[
{
"_id" : ObjectId("5dc8c47f638267be1b00e808"),
"mintTxid" : "abc371bb13034ed6acf96a39e09b22347f0038002eb8a21493032885ba6b77da",
"address" : "mokZmpYj3vSqghQaZXZ8AGt1oo1HyidLow",
"spentTxid" : "fddc7f7c6492e0cf670ff4f96e7aaaeeee3d75c51538a35286b66b6707260b46"
},
{
"_id" : ObjectId("5dc91d0d638267be1b21c2eb"),
"mintTxid" : "fddc7f7c6492e0cf670ff4f96e7aaaeeee3d75c51538a35286b66b6707260b46",
"address" : "mwE7bR8nLF9G1jUY17DzRhdWRrs4fGppvA"
}
]
I used $lookup and joined itself(spentTxid = mintTxid)
db.getCollection('coins').aggregate([
{ $match: {'address': 'mokZmpYj3vSqghQaZXZ8AGt1oo1HyidLow'}},
{
$lookup: {
from: 'coins',
localField: 'spentTxid',
foreignField: 'mintTxid',
as: 'spents'
}
},
{
$unwind: {
path: '$spents',
preserveNullAndEmptyArrays: true
}
}
])
And here is a result
{
"_id" : ObjectId("5dc8c47f638267be1b00e808"),
"mintTxid" : "abc371bb13034ed6acf96a39e09b22347f0038002eb8a21493032885ba6b77da",
"address" : "mokZmpYj3vSqghQaZXZ8AGt1oo1HyidLow",
"spentTxid" : "fddc7f7c6492e0cf670ff4f96e7aaaeeee3d75c51538a35286b66b6707260b46",
"spents" : {
"_id" : ObjectId("5dc91d0d638267be1b21c2eb"),
"mintTxid" : "fddc7f7c6492e0cf670ff4f96e7aaaeeee3d75c51538a35286b66b6707260b46",
"address" : "mwE7bR8nLF9G1jUY17DzRhdWRrs4fGppvA",
}
}
How can i get a result like this? i used $replaceRoot option, But that option return only child.
[
{
"_id" : ObjectId("5dc8c47f638267be1b00e808"),
"mintTxid" : "abc371bb13034ed6acf96a39e09b22347f0038002eb8a21493032885ba6b77da",
"address" : "mokZmpYj3vSqghQaZXZ8AGt1oo1HyidLow",
"spentTxid" : "fddc7f7c6492e0cf670ff4f96e7aaaeeee3d75c51538a35286b66b6707260b46",
},
{
"_id" : ObjectId("5dc91d0d638267be1b21c2eb"),
"mintTxid" : "fddc7f7c6492e0cf670ff4f96e7aaaeeee3d75c51538a35286b66b6707260b46",
"address" : "mwE7bR8nLF9G1jUY17DzRhdWRrs4fGppvA",
}
]
Please help me...
After aggregate add below pipeline stages and then try:
{
$project: {
array: {
$concatArrays: [
[
{
_id: "$$ROOT._id",
address: "$$ROOT.address",
mintTxid: "$$ROOT.mintTxid",
spentTxid: "$$ROOT.spentTxid",
}
],
[
"$$ROOT.spents"
]
]
}
}
},
{
$unwind: "$array"
},
{
$replaceRoot: {
newRoot: "$array"
}
}
in project we create a new array in which we push two arrays as per our requirements
unwind the array
replace root with data in ROOT

Query MongoDb to get counts from collection on array of reference id

i have total 3 collections:
1. users
2. posts
3. actions
My collections look like these:
users:
{
"_id" : ObjectId("57ee65fef5a0c032877725db"),
"fbId" : "EAAXZA4sZCZBKmgBAKe0JpJPrp7utWME6xbHT9yFD",
"name" : "Aftab",
"email" : "xxxe#hotmail.com",
"gender" : "male",
"__v" : NumberInt(0),
"updatedAt" : ISODate("2016-10-10T05:11:35.344+0000"),
"score" : NumberInt(90)
}
actions:
{
"_id" : ObjectId("57f7a0ba3a603627658afdd3"),
"updatedAt" : ISODate("2016-10-07T13:18:50.815+0000"),
"createdAt" : ISODate("2016-10-07T13:18:50.815+0000"),
"userId" : ObjectId("57ee65fef5a0c032877725db"),
"postId" : ObjectId("57f4b5e98899081203883a1b"),
"type" : "like"
}
{
"_id" : ObjectId("57f7a0ba3a603627658afdd4"),
"updatedAt" : ISODate("2016-10-07T13:18:50.815+0000"),
"createdAt" : ISODate("2016-10-07T13:18:50.815+0000"),
"userId" : ObjectId("57ee65fef5a0c032877725db"),
"postId" : ObjectId("57f4b5d58899081203883a1a"),
"type" : "dismiss"
}
posts:
{
"_id" : ObjectId("57f24593e272b5199e9351b9"),
"imgFileLocation" : "http://xxxx/buybye-platform/uploads/image-1475495315229",
"description" : "cool cool",
"title" : "Bad Image ",
"userId" : ObjectId("57f21e3d0b787d0f7ad76dd0"),
"__v" : NumberInt(0)
}
{
"_id" : ObjectId("57f4b5d58899081203883a1a"),
"imgFileLocation" : "http://xxx/buybye-platform/uploads/image-1475655125125",
"description" : "cool & cool",
"title" : "Good Image",
"userId" : ObjectId("57f21e3d0b787d0f7ad76dd0"),
"__v" : NumberInt(0)
}
user can create posts, and other users can perform actions on those posts
posts collection has a reference of userId
and actions collection has ref of userId(who performed that action), postId(on which post) and action-type(like/dislike/dismiss)
I need to query to get all actions performed on a specific user posts
I have able to get all posts against a user, which is pretty straight-forward and is an array. now I need to get all actions performed on every single post of this posts array.
If you want a solution that leverages the aggregation framework, you can use the $lookup stage that was introduced starting with MongoDB v3.2.
For example, if you want to return a result set containing the details of a post and an array of all the actions performed on that particular post, you can run the following aggregation query:
/*
* QUERY #1
*/
db.posts.aggregate([
{
$lookup: {
from: 'actions',
localField: '_id',
foreignField: 'postId',
as: 'post_actions'
}
}
]);
/*
* RESULT SET #1
*/
{
"_id" : ObjectId("57ff4512a134e614a7178c1d"),
"imgFileLocation" : "http://xxxx/buybye-platform/uploads/image-1475495315229",
"description" : "cool cool",
"title" : "Bad Image ",
"userId" : ObjectId("57f21e3d0b787d0f7ad76dd0"),
"__v" : 0,
"post_actions" : [
{
"_id" : ObjectId("57ff4563a134e614a7178c1e"),
"updatedAt" : ISODate("2016-10-07T13:18:50.815Z"),
"createdAt" : ISODate("2016-10-07T13:18:50.815Z"),
"userId" : ObjectId("57ee65fef5a0c032877725db"),
"postId" : ObjectId("57ff4512a134e614a7178c1d"),
"type" : "like"
},
{
"_id" : ObjectId("57ff4564a134e614a7178c1f"),
"updatedAt" : ISODate("2016-10-07T13:18:50.815Z"),
"createdAt" : ISODate("2016-10-07T13:18:50.815Z"),
"userId" : ObjectId("57ee65fef5a0c032877725db"),
"postId" : ObjectId("57ff4512a134e614a7178c1d"),
"type" : "share"
}
]
}
Otherwise, if you want to only retrieve the actions for a specific array of posts, you can add a $match stage in the aggregation pipeline:
const postIdsArray = [
ObjectId("57ff4512a134e614a7178c1d"),
ObjectId("57ee65fef5a0c032877725db")
];
/*
* QUERY #2
*/
db.posts.aggregate([
{
$match: {
_id: {
$in: postIdsArray
}
}
},
{
$lookup: {
from: 'actions',
localField: '_id',
foreignField: 'postId',
as: 'post_actions'
}
}
]);
Furthermore, if you would want to only retrieve the total count of actions performed on a post, you can add an $unwind stage and then $group all the results:
/*
* QUERY #3
*/
db.posts.aggregate([
{
$lookup: {
from: 'actions',
localField: '_id',
foreignField: 'postId',
as: 'post_actions'
}
},
{
$unwind: '$post_actions'
},
{
$group: {
_id: '$_id',
posts: { $sum: 1 }
}
}
]);
/*
* RESULT SET #3
*/
{ "_id" : ObjectId("57ff4512a134e614a7178c1d"), "posts" : 2 }
UPDATE #1
If you want to retrieve only the actions that are of a certain type (e.g.: like, share etc.), you can add an additional $match stage in your aggregation pipeline, after you $unwind the post_actions array retrieved in the $lookup stage.
For example, the first query would become:
/*
* UPDATED QUERY #1
*/
db.posts.aggregate([
{
$lookup: {
from: 'actions',
localField: '_id',
foreignField: 'postId',
as: 'post_actions'
}
},
{
$unwind: '$post_actions'
},
{
$match: {
"post_actions.type": 'like'
}
}
]);
The second query would become:
const postIdsArray = [
ObjectId("57ff4512a134e614a7178c1d"),
ObjectId("57ee65fef5a0c032877725db")
];
/*
* UPDATED QUERY #2
*/
db.posts.aggregate([
{
$match: {
_id: {
$in: postIdsArray
}
}
},
{
$lookup: {
from: 'actions',
localField: '_id',
foreignField: 'postId',
as: 'post_actions'
}
},
{
$unwind: '$post_actions'
},
{
$match: {
"post_actions.type": 'like'
}
}
]);
The third query would become:
/*
* UPDATED QUERY #3
*/
db.posts.aggregate([
{
$lookup: {
from: 'actions',
localField: '_id',
foreignField: 'postId',
as: 'post_actions'
}
},
{
$unwind: '$post_actions'
},
{
$match: {
"post_actions.type": 'like'
}
},
{
$group: {
_id: '$_id',
posts: { $sum: 1 }
}
}
]);

Resources