Mongoose: how to use aggregate and flatten subdocument to list - node.js

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

Related

Get _id to collect another collection detail in expressjs mongodb

collection 'bookborrow'
{
"_id" : ObjectId("62f66aa9b744696c2d08cade"),
"userId" : "62f492ace559e59ee288b73a",
"data" : {
"bookname" : "62e53c8af5d4c45fb7853d9f",
"todayDate" : "12/08/2022",
"dateofreturn" : "27/08/2022"
}
}
collection 'bookdetails'
{
"_id" : ObjectId("62e53c8af5d4c45fb7853d9f"),
"bookname" : "Steve Jobs",
"bookauthor" : "Walter Isaacson ",
"counterbooks" : "5",
"bookPublisher" : "Simon & Schuster "
}
i need bookborrow collection data.bookname in bookdetails collection (bookborrow data.bookname(62e53c8af5d4c45fb7853d9f) === _id (62e53c8af5d4c45fb7853d9f) bookdetails
i used aggregate method
const getHistoryData=await db.get().collection('bookborrow').aggregate([
{
$lookup: {
from: 'bookdetails',
let: {
bookid:
{$toObjectId: "$data.bookname"}
},
pipeline: [
{
$match: {
$expr: {
$eq: [
'$_id',
'$$bookid'
]
}
}
},
{
$project: {
bookname: 1
}
}
],
as: 'getbookdetails'
}
},
{
$unwind: '$bookid'
},
{
$project: {
_id: 1,
bookname: '$getbookdetails.bookname',
returndate: 1
}
}
])
output looklike :=
"bookname" : "Steve Jobs"
"todayDate" : "12/08/2022"
"dateofreturn" : "27/08/2022"

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

Get collection with changed one field

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

Place an object to root level in mongoDb

I have a aggregate query that fetches result from 3 collections.
I am using mongoDb 3.4
One Sample doc from result.
{
"_id" : ObjectId("5ba1717ee4b00ce08ca47cfa"),
"name" : "captain jack",
"email" : "jack#gmail.com",
"mobile" : "9000000023",
"status" : "verified",
"courses" : [
{
"_id" : "13",
"name" : "Course (03)"
},{
"_id" : "12",
"name" : "Course (03)"
}
],
"examCompleted" : false,
"login" : "5ba1717ee4b00fe08ca47cfa",
"partnerMetaInfo" : {
"_id" : ObjectId("5ba1717ee4b00fe08ca47cfa"),
"costCode" : "5761",
"hub" : "CALCUTTA",
"location" : "Kolkata"
}
}
I am trying to bring partnerMetaInfo at root level.
I am also unable to filter courses._id using $match on _id == 13
This is my aggregate query :
db.getCollection("mainCollection").aggregate([
{
//Join two collection
$lookup:{
from: "Details",
localField: "username",
foreignField: "login",
as: "partnerData"
}
},{
//Limit fields
$project:{
"email":1,
"name":1,
"mobile":1,
"status" : 1,
"courses":"$partnerData.courses",
"examScore" : "$partnerData.examScore",
"examCompleted" : "$partnerData.examCompleted",
"login":"$partnerData.login"
}
},
{
//Join third collection
$lookup:{
from: "PartnerMetaInfo",
localField: "login",
foreignField: "partnerId",
as: "partnerMetaInfo"
}
},
//Remove from partnerData array and place at root level.
{
$unwind:
{
path: '$courses',
preserveNullAndEmptyArrays: true
}
},{
$unwind:
{
path: '$examScore',
preserveNullAndEmptyArrays: true
}
},{
$unwind:
{
path: '$examCompleted',
preserveNullAndEmptyArrays: true
}
},{
$unwind:
{
path: '$login',
preserveNullAndEmptyArrays: true
}
},//Bring $partnerMetaInfo array to root level.
{
$unwind:
{
path: '$partnerMetaInfo',
preserveNullAndEmptyArrays: true
}
},{
$limit:10
}
];
partnerMetaInfo after $unwind ends up as object. I want to flatten it and bring it at root level.
Can any body help me with this?
If all you want to get as a result is the content of your partnerMetaInfo field then you can simply add a $replaceRoot stage at the end of your pipeline like this:
{
$replaceRoot: { "newRoot": { $ifNull: [ "$partnerMetaInfo", {} ] } }
}
Otherwise, in case you want to simply move the fields inside the partnerMetaInfo field to the root then you would use $addFields:
{
$addFields: {
"partnerMetaInfoId" : "$partnerMetaInfo._id",
"costCode" : "$partnerMetaInfo.costCode",
"hub" : "$partnerMetaInfo.hub",
"location" : "$partnerMetaInfo.location"
}
}
If you have a dynamic number of fields or do not want to hardcode field names then you can use the following logic:
{
$replaceRoot: { // merge fields of and move them all the way up
"newRoot": { $mergeObjects: [ "$$ROOT", "$partnerMetaInfo" ] }
}
}, {
$project: { // remove the "partnerMetaInfo" field
"partnerMetaInfo": 0
}
}

Resources