can i update specific fields in a particular collection according to the matched keys from different collections i.e suppose i have 3 collections
**///collection 1: col1///**
_id:ObjectId("#####7b")
name:'name1',
itemsBought:
[
{
"_id":ObjectId("####c1"
"itemName" : "name1",
"itemCode" : "IT001",
"itemQuantity" : 19,
"itemPrediction":23
},
{
"_id":ObjectId("####c2"
"itemName" : "name2",
"itemCode" : "IT002",
"itemQuantity" : 79,
"itemPrediction":69
},
{
"_id":ObjectId("####c3"
"itemName" : "name2",
"itemCode" : "IT003",
"itemQuantity" : 0,
"itemPrediction":0
},
]
**///collection 1: col2///**
{
"itemQuantity" : 21,
"itemCode" : "IT001",
},
{
"itemQuantity" : 2,
"itemCode" : "IT003",
}
**///collection 1: col3///**
{
"itemCode" : "IT001",
"itemPrediction":23
},
{
"itemCode" : "IT002",
"itemPrediction":12
},
{
"itemCode" : "IT003",
"itemPrediction":7
},
i am using $aggregation $lookup to fetch out all the required data, before sending it to the frontend i need to fetch the values of itemQuantity from col2 and itemPrediction from col3 and update that in col1 with the matching itemCode. So i have the query which fetches out all the data from all the collections but i dont know how to use $set to update the values in col1.
Workaround: You may perform aggregation and save the result manually
db.col1.aggregate([
{
$lookup: {
from: "col2",
let: {
root_itemCode: "$itemsBought.itemCode"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$itemCode",
"$$root_itemCode"
]
}
}
}
],
as: "col2"
}
},
{
$lookup: {
from: "col3",
let: {
root_itemCode: "$itemsBought.itemCode"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$itemCode",
"$$root_itemCode"
]
}
}
}
],
as: "col3"
}
},
{
$addFields: {
itemsBought: {
$map: {
input: "$itemsBought",
as: "item",
in: {
"_id": "$$item._id",
"itemName": "$$item.itemName",
"itemCode": "$$item.itemCode",
"itemQuantity": {
$let: {
vars: {
input: {
$arrayElemAt: [
{
$filter: {
input: "$col2",
cond: {
$eq: [
"$$item.itemCode",
"$$this.itemCode"
]
}
}
},
0
]
},
default: "$$item.itemQuantity"
},
in: {
$ifNull: [
"$$input.itemQuantity",
"$$default"
]
}
}
},
"itemPrediction": {
$let: {
vars: {
input: {
$arrayElemAt: [
{
$filter: {
input: "$col3",
cond: {
$eq: [
"$$item.itemCode",
"$$this.itemCode"
]
}
}
},
0
]
},
default: "$$item.itemPrediction"
},
in: {
$ifNull: [
"$$input.itemPrediction",
"$$default"
]
}
}
}
}
}
}
}
},
{
$unset: [
"col2",
"col3"
]
}
])
MongoPlayground
Mongoose
Collection1.aggregate([...], function (err, result) {
if(err) console.log("error-agg: " + err);
result.forEach(function(item) {
Collection1.updateOne({_id:item._id}, {$set:item}, function (err) {
if(err) console.log("error-saving: " + err);
});
});
});
Related
When use js code,i can use functional expression one by one;For example:
array.map(***).filter(...)
can i use filter after map like above in mongoose?
My question is like this.I have an dataset like below:
{
"_id" : ObjectId("5e3bd328f3dec754e1b8e17d"),
"userId" : "5e33ee0b4a3895a6d246f3ee",
"userName" : "jackiewillen",
"hasReviewedTimes" : 4,
"notes" : [
{
"time" : ISODate("2020-02-23T10:12:19.190Z"),
"memoryLine" : [
{
"hasReviewed" : false,
"_id" : ObjectId("5e51df83966daeae41e7f5b1"),
"memoryTime" : ISODate("2020-02-23T10:42:19.190Z")
},
{
"hasReviewed" : false,
"_id" : ObjectId("5e51df83966daeae41e7f5b0"),
"memoryTime" : ISODate("2020-02-23T22:12:19.190Z")
}
]
},
{
"time" : ISODate("2020-02-23T10:45:26.615Z"),
"memoryLine" : [
{
"hasReviewed" : false,
"_id" : ObjectId("5e51e746966daeae41e7f5bd"),
"memoryTime" : ISODate("2020-02-23T11:15:26.615Z")
},
{
"hasReviewed" : false,
"_id" : ObjectId("5e51e746966daeae41e7f5bc"),
"memoryTime" : ISODate("2020-02-23T22:45:26.615Z")
}
]
},
}
i use $map to get item which contain memoryTime less than now in memoryLine like below:
db.notes.aggregate([{
$match: {
"$and": [
{ userId: '5e33ee0b4a3895a6d246f3ee'}
]
}
}, {
$project: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "mLine",
cond: { $lt: ["$$mLine.memoryTime", new Date()] }
}
}
}
]
},
},
}
}
}
])
my result is like below:
"notes": [
{
"time": "2020-02-23T10:12:19.190Z",
"memoryLine": [
{
"hasReviewed": false,
"_id": "5e51df83966daeae41e7f5b1",
"memoryTime": "2020-02-23T10:42:19.190Z"
}
]
},
{ // =====> this item is not needed because of containing empty memoryLine
"time": "2020-02-23T10:45:26.615Z",
"memoryLine": [] // =======> i dont want empty item
},
]
but i want result like this:
"notes": [
{
"time": "2020-02-23T10:12:19.190Z",
"memoryLine": [
{
"hasReviewed": false,
"_id": "5e51df83966daeae41e7f5b1",
"memoryTime": "2020-02-23T10:42:19.190Z"
}
]
}
]
so i use $filter after $map to filter item which contain empty memoryLine:
db.notes.aggregate([{
$match: {
"$and": [
{ userId: '5e33ee0b4a3895a6d246f3ee'}
]
}
}, {
$project: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "mLine",
cond: { $lt: ["$$mLine.memoryTime", new Date()] }
}
}
}
],
$filter: {
input: "$$this",
as: "note",
cond: { $ne: ["$$note.memoryLine", []] }
}
},
},
}
}
}
Then this goes wrong.
You need to run another $filter as a separate pipeline stage (for readability) or as the most outer one for your current $project. I would prefer the first one:
{
$addFields: {
notes: {
$filter: {
input: "$notes",
cond: {
$ne: [ "$$this.memoryLine", [] ]
}
}
}
}
}
i want to save records in a new collection using either $out or $merge.
**/////collection 2- reservationdeatils////**
"_id":ObjectId("5e4a898947363e964a886420"),
"phoneNo" : 98765#####,
"name" : "name1",
"userId":ObjectId("5e1efac668c3c811c83263cc"),
"approversId":ObjectId("5e1efad268c3c811c83263cd")
"bookedForDate":ISODate("2020-02-20T07:23:36.130Z"),
"bookingDetails" : [
{ "_id" : ObjectId("5e44f471d1868d2a54aac12d"),
"seatsBooked" : 15,
"floorId" : "#IKE01",
},
{ "_id" : ObjectId("5e44f471d1868d2a54aac12c"),
"seatsBooked" : 35,
"floorId" : "#HKE04",
}
],
**/////collection 2-priceDetails////**
{
"_id" : ObjectId("5e1efb0168c3c811c83263ce"),
"floorId" : "#IKE01",
"weekday" : "monday",
"pricePoint" : 589,
}
{
"_id" : ObjectId("5e2694db54e532a4eb92b477"),
"floorId" : "#IKE02",
"weekday" : "thursday",
"pricePoint" : 699
}
{
"_id" : ObjectId("5e2694f954e532a4eb92b478"),
"floorId" : "#HKE04",
"weekday" : "monday",
"pricePoint" : 579
}
**/////collection 3- discount////**
{
"_id" : ObjectId("5e427de64617181a4ce38893"),
"userId" : ObjectId("5e3d05ba964d0e06c4bb0f07"),
"approversId" : ObjectId("5e1d82156a67173cb877f67d"),
"floorId" : "#IKE01",
"weekday" : "monday",
"discount" : 20%,
},
{
"_id" : ObjectId("5e4281e7fec2e01a4c60b406"),
"userId" : ObjectId("5e1efac668c3c811c83263cc"),
"approversId" : ObjectId("5e1efad268c3c811c83263cd"),
"floorId" : "#IKE01",
"weekday" : "monday",
"discount" : 24%,
}
Now below is the query i have tried :
db.reservationdeatils.aggregate([
{
'$match': {
'approverId': ObjectId('5e1efad268c3c811c83263cd'),
'userId': ObjectId('5e1efac668c3c811c83263cc'),
'bookedForDate': ISODate("2020-02-11T18:30:00Z"),
}
},
{
'$unwind': {
'path': '$bookingDetails',
},
},
{
$lookup:
{
from: 'priceDetails',
let: { floorId: '$bookingDetails.floorId' },
pipeline: [
{
$match: {
weekday: 'monday',
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
], as: 'priceDetails'
}
},
{ '$unwind': '$priceDetails' },
{
$lookup:
{
from: 'discount',
let: { floorId: '$bookingDetails.floorId' },
pipeline: [
{
$match: {
weekday: 'monday',
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
], as: 'discounts'
}
},
{ '$unwind': '$discounts' },
{
'$group': {
'_id': {
'floorId': '$bookingDetails.floorId',
'date': '$bookedForDate',
'price': '$priceDetails.pricePoint',
'discount': '$discounts.discount'
},
'seatsBooked': {
'$sum': '$bookingDetails.seatsBooked'
},
}
},
{
'$project': {
'amount': {
'$multiply':
[
'$seatsBooked',
{'$subtract':
['$_id.pricePoint',
{ '$multiply':
['$_id.pricePoint',
{ '$divide':
['$_id.discount', 100]
}]
}]
}]
},
},
},
{
$group: {
_id: null,
totalAmount: {
$sum: "$amount"
}
}
},
{
'$project': {
_id:0,
totalAmount:1,
bookedForDate:1,
'floorId':'$priceDetails.floorId'
}
},{'$merge':'invoice'}
]).pretty()
i have been able to achieve the totalAmount but what i want to achieve is that i want to save these fields into "invoice" collection "userId","approversId","floorId","sum","totalSum","bookedForDate","name" BUT 1:whenever i use $out instead of $merge the previous document gets replaces which i dont want, 2: if i use $merge everytime i run the query a new document is created and that too only with _id:ObjectId(5e4a899c47363e964a88642f),totalBill:#### these fields , any suggestion how can i achieve this
You are going in a good direction you just need to look at the $group aggregation.
One more thing I have used the discount as int value, not in percentage.
"discount" : 24
I have updated the query:
db.reservationdeatils.aggregate([
{
$match: {
"userId" : ObjectId("5e1efac668c3c811c83263cc"),
"approversId" : ObjectId("5e1efad268c3c811c83263cd"),
"bookedForDate" : ISODate("2020-02-20T07:23:36.130Z")
}
},
{
$unwind: {
path: "$bookingDetails",
},
},
{
$lookup:
{
from: "priceDetails",
let: { floorId: "$bookingDetails.floorId" },
pipeline: [
{
$match: {
weekday: "monday",
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
],
as: "priceDetails"
}
},
{
$unwind: "$priceDetails"
},
{
$lookup:
{
from: "discount",
let: { floorId: "$bookingDetails.floorId" },
pipeline: [
{
$match: {
weekday: "monday",
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
],
as: "discounts"
}
}
,
{
$unwind: "$discounts"
}
,
{
$group: {
"_id": {
"floorId": "$bookingDetails.floorId",
"date": "$bookedForDate",
"price": "$priceDetails.pricePoint",
"discount": "$discounts.discount"
},
"price":{
$first:"$priceDetails.pricePoint"
},
"discount":{
$first:"$discounts.discount"
},
"seatsBooked": {
$sum: "$bookingDetails.seatsBooked"
},
}
}
,
{
$project: {
"amount": {
$multiply:
[
"$seatsBooked",
{
$subtract:[
"$price",
{
$multiply:[
"$price",
{
$divide:[
"$discount",
100
]
}
]
}
]
}
]
},
},
}
,
{
$group: {
"_id": null,
"totalAmount": {
$sum: "$amount"
}
}
},
{
$project: {
"_id":0,
"totalAmount":1,
"bookedForDate":1,
"floorId":"$priceDetails.floorId"
}
},
{
$out:"invoice"
}
]).pretty()
This will help you.
Hi below is the description of the issue i am facing
mongoShell query
db.masters.aggregate([
{
$match: {
_id: ObjectId("5e2554ec3405363bc4bf86c0")
}
}, {
$lookup: {
from: 'masters',
localField: 'mappedVendors',
foreignField: '_id',
as: 'mappedVendors'
}
}, { $unwind: '$mappedVendors'}, { $replaceRoot: { newRoot: "$mappedVendors" } },
{
$lookup:
{
from: "orders",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$orderCreatedBy", "$$mappedVendorId"] } }
},
{ $project: { orderCreatedOn: 1, isApproved: 1 } }
],
as: "orders"
}
},{
$lookup:
{
from: "payments",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$paymentDoneBy", "$$mappedVendorId"] } }
},
{ $project: { outstanding: 1 } }
],
as: "payments"
}
},
{ $project: { name: 1, phoneNo: 1, address: 1, depotCode: 1, orders: 1, payments: 1 } }
]).pretty()
response i am getting in mongoshell
{
"_id" : ObjectId("5e2555643405363bc4bf86c4"),
"phoneNo" : 9992625541,
"name" : "vendor4",
"address" : "4 vendor address 4",
"depotCode" : "D3139",
"orders" : [ ],
"payments" : [
{
"_id" : ObjectId("5dd7aa6c31eb913a4c4a487c"),
"outstanding" : 300
}
]
}
{
"_id" : ObjectId("5e2555783405363bc4bf86c5"),
"phoneNo" : 9992625542,
"name" : "vendor5",
"address" : "5 vendor address 5",
"depotCode" : "D3139",
"orders" : [
{
"_id" : ObjectId("5e2564323405363bc4bf86c6"),
"isApproved" : false,
"orderCreatedOn" : ISODate("2020-01-20T08:26:26.812Z")
},
{
"_id" : ObjectId("5e27fd3da42d441fe8a89580"),
"isApproved" : false,
"orderCreatedOn" : ISODate("2020-01-15T18:30:00Z")
}
],
This query in shell is working as expected in shell but when i am trying this in nodejs its returning empty[].
below is the description of my nodejs file
1: Mongodb Connection string
const mongoose = require('mongoose')
mongoose.connect('mongodb://127.0.0.1:27017/#####App', {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify:false,
useUnifiedTopology: true
})
NOTE: ##### is not my code
2:nodejs controller
exports.vendorWiseIndent = async (req, res) => {
const { dealerId } = req.body
try {
const order = await Master.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(dealerId)
}
}, {
$lookup: {
from: "masters",
localField: "mappedVendors",
foreignField: "_id",
as: "mappedVendors"
},
},
{ $unwind: "$mappedVendors" }, { $replaceRoot: { newRoot: "$mappedVendors" } },
{
$lookup:
{
from: "orders",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$orderCreatedBy", "$$mappedVendorId"] } }
},
{ $project: { orderCreatedOn: 1, isApproved: 1 } }
],
as: "orders"
}
}, {
$lookup:
{
from: "payments",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$paymentDoneBy", "$$mappedVendorId"] } }
},
{ $project: { outstanding: 1 } }
],
as: "payments"
}
},
{ $project: { name: 1, phoneNo: 1, address: 1, depotCode: 1, orders: 1, payments: 1 } }
])
console.log(order)
return res.status(200).json({
order
});
} catch (error) {
res.send(error);
}}
I have also tried it with just {_id: dealerId}
3"nodejs router file
router.post("/vendorwiseindent", vendorWiseIndent.vendorWiseIndent);
POSTMAN BODY & url
POST: http://localhost:5002/vendorwiseindent
{
"dealerId": "5e2554ec3405363bc4bf86c0"
}
POSTMAN RESPONSE:
{
"order": []
}
I have also tried it with just{ _id: dealerId}
now mongodb database contains multiple collections and i have already other API's running so the db which is connected is right,there has to be some other issue that this query is not working in nodejs or rather its returning an empty array as order:[] but the query is working in shell
"mongoose": "5.7.4" & mongodb version is 4.2
nodejs controller files need be checked,
ADD
`const mongoose = require('mongoose ')`
at the top
dealerId was not getting converted to objectID as it was missing, after its addition POSTMAN response is mentioned below:
{
"order": [
{
"_id": "5e2555643405363bc4bf86c4",
"phoneNo": 9992625541,
"name": "vendor4",
"address": "4 vendor address 4",
"depotCode": "D3139",
"orders": [],
"payments": [
{
"_id": "5dd7aa6c31eb913a4c4a487c",
"outstanding": 300
}
]
},
{
"_id": "5e2555783405363bc4bf86c5",
"phoneNo": 9992625542,
"name": "vendor5",
"address": "5 vendor address 5",
"depotCode": "D3139",
"orders": [
{
"_id": "5e2564323405363bc4bf86c6",
"isApproved": false,
"orderCreatedOn": "2020-01-20T08:26:26.812Z"
},
{
"_id": "5e27fd3da42d441fe8a89580",
"isApproved": false,
"orderCreatedOn": "2020-01-15T18:30:00.000Z"
}
],
"payments": []
}
]
}
I have a question that is similar to this other question but not exactly the same because my data structure is more deeply nested, and the accepted answer did not resolve the issue.
Technologies: MongoDB 3.6, Mongoose 5.5, NodeJS 12
I am trying to query a deeply nested array of objects. The query will accept a "Start Date" and an "End Date" from the user. Item Report is an array of subdocuments that contains another array of subdocuments "Work Done By". All WorkDoneBy objects that have a "CompletedDate" in the Start and End date range should be returned along with several other properties.
Desired return properties:
RecordID, RecordType, Status, ItemReport.WorkDoneBy.DateCompleted, ItemReport.WorkDoneBy.CompletedHours, ItemReport.WorkDoneBy.Person
Record schema:
let RecordsSchema = new Schema({
RecordID: {
type: Number,
index: true
},
RecordType: {
type: String,
enum: ['Item', 'OSW']
},
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
}
}],
});
Attempt 1:
db.records.aggregate([
{
"$match": {
"ItemReport.WorkDoneBy.DateCompleted": { "$gt": new Date("2017-01-01T12:00:00.000Z"), "$lt": new Date("2018-12-31T12:00:00.000Z") }
}
},
{
"$project": {
"ItemReport.WorkDoneBy": {
"$filter": {
"input": "$ItemReport.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") ] }
]
}
}
}
}
}
])
Attempt 1 returns:
{ "_id" : ObjectId("5dcb6406e63830b7aa54269d"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
{ "_id" : ObjectId("5dcb6406e63830b7aa5426fb"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
{ "_id" : ObjectId("5dcb6406e63830b7aa542708"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
{ "_id" : ObjectId("5dcb6406e63830b7aa542712"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
Desired return (removed _id for brevity):
Note that objects in the WorkDoneBy array should be returned ONLY if they are within the specified date range. For example RecordID 9018 ItemReport.WorkDoneBy actually has dates in 2016 but those are not returned because they are not within the specified date range.
{ "ItemReport" : [ { "WorkDoneBy" : [ { "CompletedHours" : 11, "DateCompleted" : ISODate("2017-09-29T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 36, "DateCompleted" : ISODate("2018-05-18T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 32, "DateCompleted" : ISODate("2018-05-18T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") } ] } ], "RecordID" : 9018, "RecordType" : "Item", "Status" : "Done" }
{ "ItemReport" : [ { "WorkDoneBy" : [ { "CompletedHours" : 1.5, "DateCompleted" : ISODate("2017-09-01T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fe5f") } ] } ], "RecordID" : 9019, "RecordType" : "Item", "Status" : "Done" }
{ "ItemReport" : [ { "WorkDoneBy" : [ { "CompletedHours" : 2, "DateCompleted" : ISODate("2017-09-08T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 18, "DateCompleted" : ISODate("2017-09-15T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 7, "DateCompleted" : ISODate("2017-09-20T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") } ] } ], "RecordID" : 9017, "RecordType" : "Item", "Status" : "Done" }
The problem here is that WorkDoneBy is an array nested in another array (ItemReport). Therefore single $filter is not enough since you need to iterate twice. You can add $map to iterate over the outer array:
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") ] }
]
}
}
}
}
}
}
}
}
])
please check below:
db.collection.aggregate([
{
$project: {
_id: 0,
RecordID: 1,
RecordType: 1,
Status: 1,
ItemReport: {
$let: {
vars: {
wdb: {
$reduce: {
input: "$ItemReport.WorkDoneBy",
initialValue: [],
in: {
$concatArrays: [
"$$this",
"$$value"
]
}
}
}
},
in: {
WorkDoneBy: {
$filter: {
input: "$$wdb",
as: "item",
cond: {
$and: [
{
$gte: [
"$$item.DateCompleted",
ISODate("2010-01-10T04:00:00Z") // start date
]
},
{
$lte: [
"$$item.DateCompleted",
ISODate("2018-01-10T04:00:00Z") // end date
]
},
]
}
}
}
}
}
}
}
}
])
mongodb playground
I have the following metrics collection:
{
name: "Hello",
values: [
{
value: 2629,
date: "2016-10-28T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee8"
},
{
value: 1568,
date: "2016-10-29T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee7"
},
{
value: 1547,
date: "2016-10-30T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee6"
},
{
value: 1497,
date: "2016-10-31T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee5"
},
{
value: 3031,
date: "2016-11-01T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee4"
},
{
value: 2559,
date: "2016-11-02T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee3"
},
{
value: 2341,
date: "2016-11-03T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee2"
},
{
value: 2188,
date: "2016-11-04T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee1"
},
{
value: 3280,
date: "2016-11-05T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdee0"
},
{
value: 4638,
date: "2016-11-06T07:00:00.000Z",
_id: "58453abfef7aaa15ac1fdedf"
}
]
},
.... more of the same
What I would like to get is all the values between a custom date range.
I've tried the following query but I still get the entire values array returned:
{
name: "Hello",
values: {
$elemMatch: {
date: {
$lt: "2016-11-03T07:00:00.000Z",
$gt: "2016-10-28T07:00:00.000Z"
}
}
}
}
Maybe I saved my dates in a wrong format ? Any help would be greatly appreciated.
You can run an aggregation pipeline that uses the $filter operator on the values array. The following mongo shell query demonstrates this:
var start = new Date("2016-10-28T07:00:00.000Z"),
end = new Date("2016-11-03T07:00:00.000Z");
db.metrics.aggregate([
{
"$match": {
"name": "Hello",
"values.date": { "$gt": start, "$lt": end }
}
},
{
"$project": {
"name": 1,
"values": {
"$filter": {
"input": "$values",
"as": "value",
"cond": {
"$and": [
{ "$gt": [ "$$value.date", start ] },
{ "$lt": [ "$$value.date", end ] }
]
}
}
}
}
}
])
Sample Output
/* 1 */
{
"_id" : ObjectId("5845453145fda1298fa50db9"),
"name" : "Hello",
"values" : [
{
"value" : 1568,
"date" : ISODate("2016-10-29T07:00:00.000Z"),
"_id" : ObjectId("58453abfef7aaa15ac1fdee7")
},
{
"value" : 1547,
"date" : ISODate("2016-10-30T07:00:00.000Z"),
"_id" : ObjectId("58453abfef7aaa15ac1fdee6")
},
{
"value" : 1497,
"date" : ISODate("2016-10-31T07:00:00.000Z"),
"_id" : ObjectId("58453abfef7aaa15ac1fdee5")
},
{
"value" : 3031,
"date" : ISODate("2016-11-01T07:00:00.000Z"),
"_id" : ObjectId("58453abfef7aaa15ac1fdee4")
},
{
"value" : 2559,
"date" : ISODate("2016-11-02T07:00:00.000Z"),
"_id" : ObjectId("58453abfef7aaa15ac1fdee3")
}
]
}
For MongoDB 3.0, the following workaround applies:
var start = new Date("2016-10-28T07:00:00.000Z"),
end = new Date("2016-11-03T07:00:00.000Z");
db.metrics.aggregate([
{
"$match": {
"name": "Hello",
"values.date": { "$gt": start, "$lt": end }
}
},
{
"$project": {
"name": 1,
"values": {
"$setDifference": [
{
"$map": {
"input": "$values",
"as": "value",
"in": {
"$cond": [
{
"$and": [
{ "$gt": [ "$$value.date", start ] },
{ "$lt": [ "$$value.date", end ] }
]
},
"$$value",
false
]
}
}
},
[false]
]
}
}
}
])
The Aggregation Framework in MongoDB 2.2+ provides an alternative to Map/Reduce. The $unwind operator can be used to separate your values array into a stream of documents that can be matched:
db.tmp.aggregate(
// Start with a $match pipeline which can take advantage of an index and limit documents processed
{ $match : {
name: "Hello",
"values.date": {
$lt: "2016-11-03T07:00:00.000Z",
$gt: "2016-10-28T07:00:00.000Z" }
}},
{ $unwind : "$values" },
{ $match : {
name: "Hello",
"values.date": {
$lt: "2016-11-03T07:00:00.000Z",
$gt: "2016-10-28T07:00:00.000Z" }
}}
)
Sample output:
{
"_id":ObjectId("5845432720ce37bdc7e9ca1c"),
"name":"Hello",
"values":{
"value":1568,
"date":"2016-10-29T07:00:00.000Z",
"_id":"58453abfef7aaa15ac1fdee7"
}
},{
"_id":ObjectId("5845432720ce37bdc7e9ca1c"),
"name":"Hello",
"values":{
"value":1547,
"date":"2016-10-30T07:00:00.000Z",
"_id":"58453abfef7aaa15ac1fdee6"
}
},{
"_id":ObjectId("5845432720ce37bdc7e9ca1c"),
"name":"Hello",
"values":{
"value":1497,
"date":"2016-10-31T07:00:00.000Z",
"_id":"58453abfef7aaa15ac1fdee5"
}
},{
"_id":ObjectId("5845432720ce37bdc7e9ca1c"),
"name":"Hello",
"values":{
"value":3031,
"date":"2016-11-01T07:00:00.000Z",
"_id":"58453abfef7aaa15ac1fdee4"
}
},{
"_id":ObjectId("5845432720ce37bdc7e9ca1c"),
"name":"Hello",
"values":{
"value":2559,
"date":"2016-11-02T07:00:00.000Z",
"_id":"58453abfef7aaa15ac1fdee3"
}
}