I have an aggregation that returns a document, but I need it to return a document that is the latest one in the collection messageTrackingId. The one that has the most recent value in the creationDate field I have a sort to accomplish this, but I keep getting returned documents that aren't the latest and I'm not sure why. The purpose is to show the user the time stamp of the last message sent between that user and another user regardless of who sent it. appreciate any help.
app.get("/api/messages", (req, res, next) => {
query = {};
inbox = false;
messageId = false;
console.log(req.query.recipientId);
if (req.query.recipientId) {
query = { recipientId: req.query.recipientId };
inbox = true;
Messages.aggregate([
{
$match: {
$or: [
{ recipientId: req.query.recipientId },
{ creator: mongoose.Types.ObjectId(req.query.recipientId) }
]
}
},
{
$addFields: {
conversant: {
$cond: [
{ $ne: ["$recipientId", req.query.recipientId] },
"$recipientId",
"$creator"
]
}
}
},
{
$sort: { creationDate: 1 }
},
{
$group: {
_id: "$conversant",
message: { $last: "$message" },
recipientId: { $last: "$recipientId" },
creator: { $last: "$creator" },
messageTrackingId: { $last: "$messageTrackingId" },
recipient: { $last: "$recipient" },
creationDate: { $last: "$creationDate" }
}
},
{
$lookup: {
from: "users",
let: { creator: "$creator" },
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$creator"] } } },
{ $project: { instagramName: 1 } }
],
as: "creatorName"
}
}
]).then(documents => {
if (inbox === true) {
res.status(200).json({
message: "User's Inbox Retrieved!",
posts: documents
});
}
});
I cleared out the message table and sent 3 messsages back and forth to demonstrate. When I reload the page, I only receive 2 of the docs in the JSON response instead of the latest one. Of the 2 received, none of them seem to be the latest either. I'm not sure why it's not returning one. Please see output on page load below.
Messages :
/* 1 */
{
"_id" : "5e16463be5fcba1d2c56ebb7",
"creator" : "5e0a1d41f86e1e4234fc1a77",
"recipient" : "andy",
"recipientId" : "5df0014e25ee451beccf588a",
"message" : "First message",
"messageTrackingId" : "17c4fc99-d92f-46ef-9bae-1408f415287a",
"creatorName" : "buyer1",
"creationDate" : ISODate("2020-01-08T21:14:35.104Z"),
"__v" : 0.0
}
/* 2 */
{
"_id" : "5e16464de5fcba1d2c56ebb9",
"creator" : "5df0014e25ee451beccf588a",
"recipient" : "buyer1",
"recipientId" : "5e0a1d41f86e1e4234fc1a77",
"message" : "Second Message",
"messageTrackingId" : "17c4fc99-d92f-46ef-9bae-1408f415287a",
"creatorName" : "andy",
"creationDate" : ISODate("2020-01-08T21:14:53.618Z"),
"__v" : 0.0
}
/* 3 */
{
"_id" : "5e16465ce5fcba1d2c56ebbb",
"creator" : "5e0a1d41f86e1e4234fc1a77",
"recipient" : "andy",
"recipientId" : "5df0014e25ee451beccf588a",
"message" : "Third Message",
"messageTrackingId" : "17c4fc99-d92f-46ef-9bae-1408f415287a",
"creatorName" : "buyer1",
"creationDate" : ISODate("2020-01-08T21:15:08.500Z"),
"__v" : 0.0
}
users :
/* 1 */
{
"_id" : "5df00d08c713f722909c99c1",
"email" : "joe#gmail.com",
"password" : "$2b$10$5eWhwL4RNT8TyQRCC191J.myYBwFcJ8hCARqGfofYWUmRaq6jJFQm",
"instagramName" : "joe",
"over21" : "Yes",
"role" : "Artist",
"fullName" : "testdsfdfsfs",
"address1" : "adfsdf",
"address2" : "dssdf",
"city" : "sdfsdfsdf",
"state" : "dsffsdsd",
"zip" : "dsdfsdf",
"passwordCreated" : ISODate("2020-01-07T23:46:57.861Z"),
"__v" : 0
}
/* 2 */
{
"_id" : "5df0014e25ee451beccf588a",
"email" : "andy#gmail.com",
"password" : "$2b$10$fNHmIoDb4mX6x.YeMLXHbu2yIaeW6HVQvNpZR8Nt/a4xVFkhxM1Ey",
"instagramName" : "andy",
"over21" : "Yes",
"role" : "Artist",
"fullName" : "dsfdsfdfdsf",
"address1" : "111",
"address2" : "111",
"city" : "atlanta",
"state" : "ga",
"zip" : "33222",
"passwordCreated" : ISODate("2019-12-19T15:29:46.528Z"),
"__v" : 0
}
Required output (Return last message from each user) :
/* 1 */
{
"_id" : {
"conversant" : "5df0014e25ee451beccf588a",
"recipient" : "andy"
},
"message" : "Third Message",
"recipientId" : "5df0014e25ee451beccf588a",
"creator" : "5e0a1d41f86e1e4234fc1a77",
"messageTrackingId" : "17c4fc99-d92f-46ef-9bae-1408f415287a",
"recipient" : "andy",
"creationDate" : ISODate("2020-01-08T21:15:08.500Z"),
"creatorName" : [
{
"_id" : "5df0014e25ee451beccf588a",
"instagramName" : "andy"
}
]
}
/* 2 */
{
"_id" : {
"conversant" : "5df0014e25ee451beccf588a",
"recipient" : "buyer1"
},
"message" : "Second Message",
"recipientId" : "5e0a1d41f86e1e4234fc1a77",
"creator" : "5df0014e25ee451beccf588a",
"messageTrackingId" : "17c4fc99-d92f-46ef-9bae-1408f415287a",
"recipient" : "buyer1",
"creationDate" : ISODate("2020-01-08T21:14:53.618Z"),
"creatorName" : [
{
"_id" : "5df0014e25ee451beccf588a",
"instagramName" : "andy"
}
]
}
Changes has to be
1) If I understand correctly at this stage you wanted to group by recipientId on a field(since you can't group on two fields for a single value) So you're creating a new field called conversant, So it has to be $eq or you need to swap recipientId & creator & also it has to be $toString:
{
$addFields: {
conversant: {
$cond: [
{ $eq: ["$recipientId", req.query.recipientId] },
"$recipientId",
{$toString : "$creator"}
]
}
}
}
2) You don't need this $sort stage, as all latest additions to collection will be added at the end (Expecting creationDate is once created but never updated) :
// Not useful
{
$sort: { creationDate: 1 }
}
Also you need to group by _id: { conversant: "$conversant", recipient: '$recipient' } for group on last messages based on user. In $lookup change to this let: { conversant: "$_id.conversant" }.
Try this :
Messages.aggregate([
{
$match: {
$or: [
{ recipientId: req.query.recipientId },
{ creator: mongoose.Types.ObjectId(req.query.recipientId) }
]
}
},
{
$addFields: {
conversant: {
$cond: [
{ $eq: ["$recipientId", req.query.recipientId] },
"$recipientId",
{ $toString: "$creator" }
]
}
}
},
{
$group: {
_id: { conversant: "$conversant", recipient: '$recipient' },
message: { $last: "$message" },
recipientId: { $last: "$recipientId" },
creator: { $last: "$creator" },
messageTrackingId: { $last: "$messageTrackingId" },
recipient: { $last: "$recipient" },
creationDate: { $last: "$creationDate" }
}
},
{
$lookup: {
from: "users",
let: { conversant: "$_id.conversant" },
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$conversant"] } } },
{ $project: { instagramName: 1 } }
],
as: "creatorName"
}
},
// this is optional only if you wanted to return latest document irrespective of user.
{ $sort: { creationDate: -1 } }
])
Related
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"
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.
My db cofiguration looks like:
{
"_id" : ObjectId("5ece47aa6510a611b47aac5a"),
"boats" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac6e"),
"model" : "Dufour",
"year" : 2019,
"about" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 14,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 200
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 120
},
]
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 8,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "benzin",
"fuelCap" : 180
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 100
},
]
},
{...},
{...},
]
}
Now i am trying to count the number of boats which have "fuel" : "petrol", so i use the code bellow:
router.get('/boat', async(req, res)=>{
try{
const fuelData = await Boat.aggregate([
{
$project: {
fuelData: {
$filter: {
input: "$boats",
as: "boats",
cond: {
$filter:{
input:"$$boats.about",
as:"about",
cond:{
$filter:{
input:"$$about.characteristics",
as:"characteristics",
cond:{
$eq:["$$activity1.activity.type", "STILL"]
}
}
}
}
}
}
}
}
},
{
$project: {
boatsCount: {$size : "$fuelData" }
}
}
])
res.status(201).send(fuelData)
}catch(e){
res.send(e)
}
})
The problem is that return wrong number of boatCount. And it seems like it returns the number of the boats which are inside the db. Any help how to count correctly the boats which have "fuel" : "petrol"?
Is there anything wrong in my code?
https://mongoplayground.net/p/D0FEhTMJEJ1
Hope this is what you need.
The sample data you have provided is missing a ] & }.
So I added 1 additional boat with 2 about.
db.collection.aggregate([
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$unwind: "$boats"
},
{
$unwind: "$boats.about"
},
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
])
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
}
}
I have nested data as below,
{
"_id" : ObjectId("5a30ee450889c5f0ebc21116"),
"academicyear" : "2017-18",
"fid" : "be02",
"fname" : "ABC",
"fdept" : "Comp",
"degree" : "BE",
"class" : "1",
"sem" : "8",
"dept" : "Comp",
"section" : "Theory",
"subname" : "BDA",
"fbValueList" : [
{
"_id" : ObjectId("5a30eecd3e3457056c93f7af"),
"score" : 20,
"rating" : "Fair"
},
{
"_id" : ObjectId("5a30eefd3e3457056c93f7b0"),
"score" : 10,
"rating" : "Fair"
},
{
"_id" : ObjectId("5a337e53341bf419040865c4"),
"score" : 88,
"rating" : "Excellent"
},
{
"_id" : ObjectId("5a337ee2341bf419040865c7"),
"score" : 75,
"rating" : "Very Good"
},
{
"_id" : ObjectId("5a3380b583dde50ddcea350e"),
"score" : 72,
"rating" : "Very Good"
}
]
},
{
"_id" : ObjectId("5a3764f1bc19b77dd9fd9a57"),
"academicyear" : "2017-18",
"fid" : "be02",
"fname" : "ABC",
"fdept" : "Comp",
"degree" : "BE",
"class" : "1",
"sem" : "5",
"dept" : "Comp",
"section" : "Theory",
"subname" : "BDA",
"fbValueList" : [
{
"_id" : ObjectId("5a3764f1bc19b77dd9fd9a59"),
"score" : 88,
"rating" : "Excellent"
},
{
"_id" : ObjectId("5a37667aee64bce1b14747d2"),
"score" : 74,
"rating" : "Good"
},
{
"_id" : ObjectId("5a3766b3ee64bce1b14747dc"),
"score" : 74,
"rating" : "Good"
}
]
}
We are trying to perform aggregation using this,
db.fbresults.aggregate([{$match:{academicyear:"2017-18",fdept:'Comp'}},{$group:{_id: {fname: "$fname", rating:"$fbValueList.rating"},count: {"$sum":1}}}])
and we get result like,
{ "_id" : { "fname" : "ABC", "rating" : [ "Fair","Fair","Excellent","Very Good", "Very Good", "Excellent", "Good", "Good" ] }, "count" : 2 }
but we are expecting result like,
{ "_id" : { "fname" : "ABC", "rating_group" : [
{
rating: "Excellent"
count: 2
},
{
rating: "Very Good"
count: 2
},
{
rating: "Good"
count: 2
},
{
rating: "Fair"
count: 2
},
] }, "count" : 2 }
We want to get individual faculty group by their name and inside that group by their rating response and count of rating.
We have already tried this one but we did not the result.
Mongodb Aggregate Nested Group
This should get you going:
db.collection.aggregate([{
$match: {
academicyear: "2017-18",
fdept:'Comp'
}
}, {
$unwind: "$fbValueList" // flatten the fbValueList array into multiple documents
}, {
$group: {
_id: {
fname: "$fname",
rating:"$fbValueList.rating"
},
count: {
"$sum": 1 // this will give us the count per combination of fname and fbValueList.rating
}
}
}, {
$group: {
_id: "$_id.fname", // we only want one bucket per fname
rating_group: {
$push: { // we push the exact structure you were asking for
rating: "$_id.rating",
count: "$count"
}
},
count: {
$avg: "$count" // this will be the average across all entries in the fname bucket
}
}
}])
This is a long aggregation pipeline, there may be some aggregations that are un-necessary, so please check and discard whichever are irrelevant.
NOTE: This will only work with Mongo 3.4+.
You need to use $unwind and then $group and $push ratings with their counts.
matchAcademicYear = {
$match: {
academicyear:"2017-18", fdept:'Comp'
}
}
groupByNameAndRating = {
$group: {
_id: {
fname: "$fname", rating:"$fbValueList.rating"
},
count: {
"$sum":1
}
}
}
unwindRating = {
$unwind: "$_id.rating"
}
addFullRating = {
$addFields: {
"_id.full_rating": "$count"
}
}
replaceIdRoot = {
$replaceRoot: {
newRoot: "$_id"
}
}
groupByRatingAndFname = {
$group: {
_id: {
"rating": "$rating",
"fname": "$fname"
},
count: {"$sum": 1},
full_rating: {"$first": "$full_rating"}
}
}
addFullRatingAndCount = {
$addFields: {
"_id.count": "$count",
"_id.full_rating": "$full_count"
}
}
groupByFname = {
$group: {
_id: "$fname",
rating_group: { $push: {rating: "$rating", count: "$count"}},
count: { $first: "$full_rating"}
}
}
db.fbresults.aggregate([
matchAcademicYear,
groupByNameAndRating,
unwindRating,
addFullRating,
unwindRating,
replaceIdRoot,
groupByRatingAndFname,
addFullRatingAndCount,
replaceIdRoot,
groupByFname
])