This is Code in node js
const result = await OrderDB.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(id) } },
{
$lookup: {
from: 'products',
localField: 'product',
foreignField: '_id',
as: 'productDetail',
},
},
{
$project: {
productDetail: {
name: 1,
price: 1,
productImage: 1,
},
},
},
])
This is the response of code
{
"message": "Get Order Successfully",
"result": [
{
"_id": "5ff47348db5f5917f81871aa",
"productDetail": [
{
"name": "Camera",
"productImage": [
{
"_id": "5fe9b8a26720f728b814e246",
"img": "uploads\\product\\7Rq1v-app-7.jpg"
},
{
"_id": "5fe9b8a26720f728b814e247",
"img": "uploads\\product\\FRuVb-app-8.jpg"
}
],
"price": 550
}
]
}
]
}
I want to display only one productImage from the response using nodejs and mongoose
This is using in aggregate projection
I was use $arrayElemAt but it is don't work
I also use $first but it is don't work
so projection method I use to display only one *productImage*
Data looks like multi level nested.
You have array of results, each result contains array of productDetails
play
You need to unwind the data to get the first productImage
db.collection.aggregate([
{
"$unwind": "$result"
},
{
"$unwind": "$result.productDetail"
},
{
$project: {
pImage: {
"$first": "$result.productDetail.productImage"
}
}
}
])
With the above response
db.collection.aggregate([
{
"$project": {
productDetails: {
$map: {
input: "$productDetail",
in: {
"$mergeObjects": [
"$$this",
{
productImage: {
"$arrayElemAt": [
"$$this.productImage",
0
]
}
}
]
}
}
}
}
}
])
Working Mongo playground
Related
I have two related collections that contain documents as follows:
/* heroes */
{ id: "HID_1", name: "A" }
{ id: "HID_2", name: "B" }
/* weapons */
{ name: "WHID_1", weapon: "Sword" }
{ name: "WHID_2", weapon: "Lance" }
How can I aggregate them so I get a single document where I know "A" uses a Sword and "B" uses a Lance? I can't directly join them by id and name because their value isn't exactly the same, but Weapon has a W-prefix on it.
I made some attempts with $substr but no success so far.
db.heroes.aggegate( [
{
$lookup: {
from: 'weapons',
let: { heroId: '$id' },
pipeline: [
{
$match: {
$expr: {
$eq: [ '$$heroId', { $substr: [ '$name', 1, -1 ] } ]
}
}
}
],
as: 'weapon'
}
}
] )
For reference, I also tried just hard-coding an ID with { $match: { $expr: { $eq: [ '$$heroId', 'HID_1' ] } } } and it didn't work. I could just rename all WHID to HID, but I am curious about whether it is possible or not.
Use $project to append the "W" to the heroID and then do a regular lookup like described here:
https://stackoverflow.com/a/46969468
I am laughing so hard right now, the query I posted is not the same I have in my code, and apparently I fixed it without knowing while I was copying it into the question. My let was wrong and defined weapons.name instead of heroes.id.
For anyone having a similar issue, the aggregate in the original post works as it should. I didn't notice it until #varman pointed it out, so thank you! And sorry for the silly mistake.
Try this...
db.heroes.aggregate([
{
$project: {
_id: 1,
name: 1,
newID: {
$concat: [
"W",
"$_id"
]
}
}
},
{
"$lookup": {
"from": "weapons",
localField: "newID",
foreignField: "name",
"as": "data"
}
},
{
$unwind: "$data"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$data",
"$$ROOT"
]
}
}
},
{
$project: {
data: 0,
newID: 0
}
}
])
or
db.heroes.aggregate([
{
$lookup: {
from: "weapons",
let: {
heroId: "$id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$$heroId",
{
$substr: [
"$name",
1,
-1
]
}
]
}
}
}
],
as: "data"
}
},
{
$unwind: "$data"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$data",
"$$ROOT"
]
}
}
},
{
$project: {
data: 0
}
}
])
output:
[
{
"_id": "HID_1",
"name": "A",
"weapon": "Sword"
},
{
"_id": "HID_2",
"name": "B",
"weapon": "Lance"
}
]
Mongoplayground
I'm trying to get only object pasien_docs in result of lookup, I using match, lookup and addfield but i want get result only show array of pasien_docs:
Pasien.aggregate(
[
{
$match: {
_id: ObjectId(id)
}
},
{
$lookup:
{
from: "data",
localField: "_id",
foreignField: "idPasien",
as: "pasien_docs"
}
},
{ '$addFields': {
'pasien_docs': {
'$map': {
'input': '$pasien_docs',
'in': {
'ecg': '$$this.ecg',
'date': '$$this.date'
}
}
}
}}
]
)
and the result is :
"data": [
{
"_id": "5e08a79fe34fa20a302f659f",
"username": "rizkarahayus",
"nama": "Rizka Rahayu",
"__v": 0,
"pasien_docs": [
{
"ecg": null,
"date": "2020-01-21T05:22:01.901Z"
},
{
"ecg": 1.03,
"date": "2020-01-21T05:22:02.979Z"
},
but i want result only show array of pasien_docs like this :
"pasien_docs": [
{
"ecg": null,
"date": "2020-01-21T05:22:01.901Z"
},
{
"ecg": 1.03,
"date": "2020-01-21T05:22:02.979Z"
},
can you help me? thank you...
You can simply use $project and specify the inclusion of pasien_docs computed field, the suppression of the _id field like:
Pasien.aggregate(
[
{
$match: {...}
},
{
$lookup: {...}
},
{
'$addFields': {...}
},
{
$project: { _id: 0, pasien_docs: 1 }
}
]
)
So these are my two documents
Order document:
{
"_id":"02a33b9a-284c-4869-885e-d46981fdd679",
"context":{
"products":[
{
"id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"version": "2020-03-14T13:18:41.296+00:00"
}
],
},
}
Product document:
{
"_id":"e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"context":{
"name": "My Product",
"image": "someimage"
},
}
So I'm trying to do a lookup for a products in order document, but the result should contain combined fields, like so:
"products":[
{
"_id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"version": "2020-03-14T13:18:41.296+00:00",
"name": "My Product",
"image": "someimage"
}
],
Not sure how to do this, should I do it outside of the lookup, or inside? This is my aggregation
Orders.aggregate([
{
"$lookup":{
"from":"products",
"let":{
"products":"$context.products"
},
"pipeline":[
{
"$match":{
"$expr":{
"$in":[
"$_id",
"$$products.id"
]
}
}
},
{
"$project":{
"_id":0,
"id":1,
"name":"$context.name"
}
}
],
"as":"mergedProducts"
}
},
{
"$project":{
"context":"$context",
"mergedProducts":"$mergedProducts"
}
},
]);
You need to run that mapping outside of $lookup by running $map along with $arrayElemAt to get single pair from both arrays and then apply $mergeObjects to get one object as a result:
db.Order.aggregate([
{
$lookup: {
from: "products",
localField: "context.products.id",
foreignField: "_id",
as: "productDetails"
}
},
{
$addFields: {
productDetails: {
$map: {
input: "$productDetails",
in: {
_id: "$$this._id",
name: "$$this.context.name"
}
}
}
}
},
{
$project: {
_id: 1,
"context.products": {
$map: {
input: "$context.products",
as: "prod",
in: {
$mergeObjects: [
"$$prod",
{ $arrayElemAt: [ { $filter: { input: "$productDetails", cond: { $eq: [ "$$this._id", "$$prod.id" ] } } }, 0 ] }
]
}
}
}
}
}
])
Mongo Playground
The goals of the last step is to take take two arrays: products and productDetails (the output of $lookup) and find matches between them. We know there's always one match so we can get only one item $arrayElemAt 0. As an output of $map there will be single array containing "merged" documents.
This question is a follow up to a previous question for which I have accepted an answer already. I have an aggregate query that returns the results of a deeply nested array of subdocuments based on a date range. The query returns the correct results within the specified date range, however it also returns an empty array for the results that do not match the query.
Technologies: MongoDB 3.6, Mongoose 5.5, NodeJS 12
Question 1:
Is there any way to remove the results that don't match the query?
Question 2:
Is there any way to 'populate' the Person db reference in the results? For example to get the Person Display Name I usually use 'populate' such as find().populate({ path: 'Person', select: 'DisplayName'})
Records schema
let RecordsSchema = new Schema({
RecordID: {
type: Number,
index: true
},
RecordType: {
type: String
},
Status: {
type: String
},
// ItemReport array of subdocuments
ItemReport: [ItemReportSchema],
}, {
collection: 'records',
selectPopulatedPaths: false
});
let ItemReportSchema = new Schema({
// ObjectId reference
ReportBy: {
type: Schema.Types.ObjectId,
ref: 'people'
},
ReportDate: {
type: Date,
required: true
},
WorkDoneBy: [{
Person: {
type: Schema.Types.ObjectId,
ref: 'people'
},
CompletedHours: {
type: Number,
required: true
},
DateCompleted: {
type: Date
}
}],
});
Query
Works but also returns empty results and also need to populate the Display Name property of the Person db reference
db.records.aggregate([
{
"$project": {
"ItemReport": {
$map: {
input: "$ItemReport",
as: "ir",
in: {
WorkDoneBy: {
$filter: {
input: "$$ir.WorkDoneBy",
as: "value",
cond: {
"$and": [
{ "$ne": [ "$$value.DateCompleted", null ] },
{ "$gt": [ "$$value.DateCompleted", new Date("2017-01-01T12:00:00.000Z") ] },
{ "$lt": [ "$$value.DateCompleted", new Date("2018-12-31T12:00:00.000Z") ] }
]
}
}
}
}
}
}
}
}
])
Actual Results
{
"_id": "5dcb6406e63830b7aa5427ca",
"ItemReport": [
{
"WorkDoneBy": [
{
"_id": "5dcb6406e63830b7aa53d8ea",
"PersonID": 111,
"ReportID": 8855,
"CompletedHours": 3,
"DateCompleted": "2017-01-20T05:00:00.000Z",
"Person": "5dcb6409e63830b7aa54fdba"
}
]
}
]
},
{
"_id": "5dcb6406e63830b7aa5427f1",
"ItemReport": [
{
"WorkDoneBy": [
{
"_id": "5dcb6406e63830b7aa53dcdc",
"PersonID": 4,
"ReportID": 9673,
"CompletedHours": 17,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person": "5dcb6409e63830b7aa54fd69"
},
{
"_id": "5dcb6406e63830b7aa53dcdd",
"PersonID": 320,
"ReportID": 9673,
"CompletedHours": 3,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person": "5dcb6409e63830b7aa54fe88"
}
]
}
]
},
{
"_id": "5dcb6406e63830b7aa5427f2",
"ItemReport": [
{
"WorkDoneBy": []
}
]
},
{
"_id": "5dcb6406e63830b7aa5427f3",
"ItemReport": [
{
"WorkDoneBy": []
}
]
},
{
"_id": "5dcb6406e63830b7aa5427f4",
"ItemReport": [
{
"WorkDoneBy": []
}
]
},
{
"_id": "5dcb6406e63830b7aa5427f5",
"ItemReport": [
{
"WorkDoneBy": []
}
]
},
Desired results
Note the results with an empty "WorkDoneBy" array are removed (question 1), and the "Person" display name is populated (question 2).
{
"_id": "5dcb6406e63830b7aa5427f1",
"ItemReport": [
{
"WorkDoneBy": [
{
"_id": "5dcb6406e63830b7aa53dcdc",
"CompletedHours": 17,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person": {
_id: "5dcb6409e63830b7aa54fe88",
DisplayName: "Joe Jones"
}
},
{
"_id": "5dcb6406e63830b7aa53dcdd",
"CompletedHours": 3,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person": {
_id: "5dcb6409e63830b7aa54fe88",
DisplayName: "Alice Smith"
}
}
]
}
]
},
First question is relatively easy to answer and there are multiple ways to do that. I would prefer using $anyElementTrue along with $map as those operators are pretty self-explanatory.
{
"$match": {
$expr: { $anyElementTrue: { $map: { input: "$ItemReport", in: { $gt: [ { $size: "$$this.WorkDoneBy" }, 0 ] } } } }
}
}
MongoPlayground
Second part is a bit more complicated but still possible. Instead of populate you need to run $lookup to bring the data from other collection. The problem is that your Person values are deeply nested so you need to prepare a list of id values before using $reduce and $setUnion. Once you get the data you need to merge your nested objects with people entities using $map and $mergeObjects.
{
$addFields: {
people: {
$reduce: {
input: "$ItemReport",
initialValue: [],
in: { $setUnion: [ "$$value", "$$this.WorkDoneBy.Person" ] }
}
}
}
},
{
$lookup: {
from: "people",
localField: "peopleIds",
foreignField: "_id",
as: "people"
}
},
{
$project: {
_id: 1,
ItemReport: {
$map: {
input: "$ItemReport",
as: "ir",
in: {
WorkDoneBy: {
$map: {
input: "$$ir.WorkDoneBy",
as: "wdb",
in: {
$mergeObjects: [
"$$wdb",
{
Person: { $arrayElemAt: [{ $filter: { input: "$people", cond: { $eq: [ "$$this._id", "$$wdb.Person" ] } } } , 0] }
}
]
}
}
}
}
}
}
}
}
Complete Solution
I have a visits collection where I successfully count the number of visits per location
Visits model:
{
"_id": {
"$oid": "5a3969e2f4ea3e33ac5a523d"
},
"locationId": "5a395ccf210a1d35d0df4a58"
}
locationId above is of type 'Object' as I learned lookup localField and foreignField must be of same type
nodejs code =>
let sort = { "count": -1, "locationId": 1 };
Visit.aggregate([
{
$match:{
$and: [
{ accountId: req.session.passport.user },
{
'details.dateTimeIn': {
$gte: new Date(dateFrom), $lte: new Date(dateTo)
}
}
]
}
},
{
"$group": {
//_id: name,
_id: "$locationId",
count: { $sum: 1 }
}
},
{ $sort: sort }
])
Output is half ok:
[
{
"_id":"5a395ccf210a1d35d0df4a58",
"count":20
}
]
Instead of showing location id id like to show location name. Schema for locations collection is:
{
"_id": {
"$oid": "5a395ccf210a1d35d0df4a58"
"name": "Tower A",
"__v": 0
}
}
Research suggests I need to use $lookup to get that JOIN effect
So I tried
{
"$lookup": {
from: "locations",
localField: "_id",
foreignField: "_id",
as: "locationdetails"
}
}
but the match seems broken. The closest I got was a list of all locations in 'locationdetails'
But with code above here is the empty locationdetails
[
{
"_id":"5a395ddf1d221918d0041313",
"count":20,
"locationdetails":[
]
}
]
What am I missing ?