I want to get some objects from array of objects in a mongodb document
my document looks something like this
{
movieDetails: {
movieId: 333,
movieName: "movie",
},
Dates: [
{
Date: " 01/07/2021",
TheaterId: 12,
Is3D: "false",
},
{
Date: " 09/07/2021",
TheaterId: 13,
Is3D: "false",
},
{
Date: " 03/07/2021",
TheaterId: 12,
Is3D: "false",
}
]
}
I want to get only the objects where the movieId is equal to 333 and TheaterId is 12 my result should look like this
[
{
Date: " 01/07/2021",
TheaterId: 12,
Is3D: "false",
},
{
Date: " 03/07/2021",
TheaterId: 12,
Is3D: "false",
}
]
I tried this
const dates = await Movie.find("movieDetails.movieId": 333).select({ Dates: {$elemMatch: {TheaterId: 12}}});
But it returns only the first object if anyone can help me with this I will be very grateful
According to $elemMatch docs:
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
So you can use this aggregation pipeline using $filter into a $project stage like this:
Movie.aggregate([
{
"$match": {
"movieDetails.movieId": 333
}
},
{
"$project": {
"movieDetails": 1,
"Dates": {
"$filter": {
"input": "$Dates",
"as": "d",
"cond": {
"$eq": [
"$$d.TheaterId",
12
]
}
}
}
}
}
])
Example here
Or this another pipeline (I prefer using $filter)
Movie.aggregate([
{
"$match": {
"movieDetails.movieId": 333
}
},
{
"$unwind": "$Dates"
},
{
"$match": {
"Dates.TheaterId": 12
}
},
{
"$group": {
"_id": "$_id",
"movieDetails": {
"$first": "$movieDetails"
},
"Dates": {
"$push": "$Dates"
}
}
}
])
Example here
I would suggest taking a look at the doc for .find() since it seems you are using it incorrectly.
One way you can achieve this is to use .find() to find the movie with the ID you want, then .filter() the Dates from that object to return just the ones with your TheaterId
here is an example
const movies = [{
movieDetails: {
movieId: 333,
movieName: "movie",
},
Dates: [{
Date: " 01/07/2021",
TheaterId: 12,
Is3D: "false",
},
{
Date: " 09/07/2021",
TheaterId: 13,
Is3D: "false",
},
{
Date: " 03/07/2021",
TheaterId: 12,
Is3D: "false",
}
]
}]
function findMovieTimesByTheater(movieID, theaterID) {
const movie = movies.find(movie => movie.movieDetails.movieId === movieID);
if (typeof movie === "undefined") {
console.log(`movieID: ${movieID} not found`);
return [];
}
return movie.Dates.filter(date => date.TheaterId === theaterID);
}
const times = findMovieTimesByTheater(333, 12);
console.log(times);
Related
I have the following document in MongoDB
{
"product_id": "10001"
"product_name": "Banana"
"product_date": "2022-10-20T00:00:00.000+00:00"
"product_price": 255.15
"dates": {
"2022-10-10": {
"recorded_price": 195.15
},
"2022-10-15": {
"recorded_price": 230.20
},
"2022-10-20": {
"recorded_price": 255.20
}
}
}
I would like to add a new field named "min_7day_price" which would select the minimum price from the date object in the past 7 days.
Something like this:
{
"product_id": "10001"
"product_name": "Banana"
"product_date": "2022-10-20T00:00:00.000+00:00"
"product_price": 255.15
"dates": {
"2022-10-10": {
"recorded_price": 195.15
},
"2022-10-15": {
"recorded_price": 230.20
},
"2022-10-20": {
"recorded_price": 255.20
}
},
"min_7day_price": 230.20
}
I tried using aggregation to create a new field and convert the object to an array but I can't filter the values inside.
{
"min_7day_price": {
$objectToArray: "$dates"
}
}
One option is to use update with pipeline:
Convert the dictionary to array
Use $reduce to keep only one item from it, by iterating and comparing the current item: $$this to the best item so far: $$value
Format the answer
db.collection.update({},
[
{$set: {datesArr: {$objectToArray: "$dates"}}},
{$set: {
datesArr: {
$reduce: {
input: "$datesArr",
initialValue: {
k: {$dateAdd: {startDate: "$$NOW", amount: -7, unit: "day"}},
v: {recorded_price: {$max: "$datesArr.v.recorded_price"}}
},
in: {
$cond: [
{$and: [
{$gte: [{$dateFromString: {dateString: "$$this.k"}}, "$$value.k"]},
{$lte: ["$$this.v.recorded_price", "$$value.v.recorded_price"]}
]},
{
k: {$dateFromString: {dateString: "$$this.k"}},
v: "$$this.v.recorded_price"
},
"$$value"
]
}
}
}
}
},
{$set: {min_7day_price: "$datesArr.v", datesArr: "$$REMOVE"}}
])
See how it works on the playground example
I have an object as follows.
cityConfiguration -
{
_id: 62e135519567726de42421c2,
configType: 'cityConfig'
'16': {
cityName: 'Delhi',
metro: true,
tier1: true
},
'17': {
cityName: 'Indore',
metro: false,
tier1: false
}
}
I have another table called BusDetails with fields like - [cityId, revenue, tax...]. Example document -
{
"busNumber": "KA25D5057",
"cityId": "17",
"revenue": 46594924,
"totalCollection": 3456342
"tax": "2906",
"passengerCount": 40
......
}
I want to do an aggregation ($project) on the BusDetails table such that I take the cityId from that BusDetails document and if that cityId is a metro city (according to the cityConfig object) then I will return 0 or 1. I have designed the below code but its not working. How to fix it.
return await BsDetails.aggregate([
{
$match: { busNumber: 'KA25D5057' }
},
{
$project: {
totalCollection: 1,
passengerCount: 1,
.
.
.
metro: {"$cond": [ cityPassConfig["$cityId"].metro == true, 1, 0]},
}
}
]);
So for ex, in the above code, the cityId is 17 which is a non-metro city. So the "metro" field should store 0. Here I am facing problem in
cityPassConfig["$cityId"].metro
this particular statement.
Using objects with dynamic field name is generally considered as anti-pattern and should be avoided since it introduces unnecessary complexity into queries. Nevertheless, you can use $objectToArray to achieve what you want.
db.BusDetails.aggregate([
{
$match: {
busNumber: "KA25D5057"
}
},
{
"$addFields": {
// put your object here
"cityConfiguration": {
_id: ObjectId("62e135519567726de42421c2"),
configType: "cityConfig",
"16": {
cityName: "Delhi",
metro: true,
tier1: true
},
"17": {
cityName: "Indore",
metro: false,
tier1: false
}
}
}
},
{
"$addFields": {
// convert into array of k-v tuples
"cityConfiguration": {
"$objectToArray": "$cityConfiguration"
}
}
},
{
"$addFields": {
"cityConfiguration": {
// iterate through the array and get the matched cityConfiguration entry
"$reduce": {
"input": "$cityConfiguration",
"initialValue": null,
"in": {
"$cond": {
"if": {
$eq: [
"$$this.k",
"$cityId"
]
},
"then": "$$this.v",
"else": "$$value"
}
}
}
}
}
},
{
$project: {
totalCollection: 1,
passengerCount: 1,
metro: {
"$cond": {
"if": "$cityConfiguration.metro",
"then": 1,
"else": 0
}
}
}
}
])
Here is the Mongo playground for your reference.
I've been looking to get data for the last n days. For example, I want to just all the data of the last 3 days. After implementing a solution from another question in StackOverflow I'm not getting all the documents. I am only getting one document. If I want to see the documents for the last 3 days it is only showing data for one particular day.
Here's my Schema:
dayWiseClicks: [
{
date: {
type: Date,
},
dailyClicks: {
type: Number,
default: 0,
},
},
],
Here's the data I have before performing the query:
{
"_id": {
"$oid": "61eff4bacf8335c7013f8065"
},
"dayWiseClicks": [
{
"dailyClicks": 3,
"_id": {
"$oid": "61eff5db5dca56cae4530db2"
},
"date": {
"$date": "2022-01-24T18:00:00.000Z"
}
},
{
"dailyClicks": 4,
"_id": {
"$oid": "61eff60b5dca56cae4530db6"
},
"date": {
"$date": "2022-01-25T18:00:00.000Z"
}
},
{
"dailyClicks": 2,
"_id": {
"$oid": "61eff64a5dca56cae4530dba"
},
"date": {
"$date": "2022-01-26T18:00:00.000Z"
}
},
{
"dailyClicks": 7,
"_id": {
"$oid": "61f60ce51f14b01f8f5be936"
},
"date": {
"$date": "2022-01-29T18:00:00.000Z"
}
},
{
"dailyClicks": 11,
"_id": {
"$oid": "61f7b1d3931b0f8bc33703d4"
},
"date": {
"$date": "2022-01-30T18:00:00.000Z"
}
},
{
"dailyClicks": 8,
"_id": {
"$oid": "61f8bdf63cc3a51b72474cb9"
},
"date": {
"$date": "2022-01-31T18:00:00.000Z"
}
},
{
"dailyClicks": 7,
"_id": {
"$oid": "61fba7159692624ce8ea04d6"
},
"date": {
"$date": "2022-02-02T18:00:00.000Z"
}
}
],
}
In theory, if I want to see the last 3 days of data. It should be showing data of 31st Jan, 2nd February but It is only showing Data of 31st January.
Here's the data I am getting:
{
"message": "Url By ID",
"result": {
"_id": "61eff4bacf8335c7013f8065",
"dayWiseClicks": [
{
"dailyClicks": 8,
"_id": "61f8bdf63cc3a51b72474cb9",
"date": "2022-01-31T18:00:00.000Z"
}
]
}
}
Here's my Code:
exports.lastNDays = async (req, res) => {
try {
const url = await URL.findById(
{ _id: req.params.id },
{
dayWiseClicks: {
$elemMatch: {
date: {
$gte: moment().add(-3, "days"),
},
},
},
}
)
return res.status(200).json({
message: "Url By ID",
result: url,
});
} catch (error) {
return res.status(404).json({ error: error.message });
}
};
Can any one tell me exactly where I am making the mistake?
The aggregation->$filter option seems more suitable for the task , example:
db.collection.aggregate([{
$match: {
"_id": {
"$oid": "61eff4bacf8335c7013f8065"
}
}
},
{
$project: {
dayWiseClicks: {
$filter: {
input: "$dayWiseClicks",
as: "item",
cond: {
$gte: [
"$$item.date",
{
"$date": "2022-01-30T18:00:00.000Z"
}
]
}
}
}
}
}
])
Explained:
$match single document by _id
$filter only the dayWiseClicks greater or equal to certain date.
playground
If your dayWiseClicks are in natural date-ascending order (and it seems that way), then to generically capture the last 3 days of any sequence of dates can be done with $slice:
db.foo.aggregate([
{$addFields: {dayWiseClicks: {$slice: ["$dayWiseClicks", -3]}}}
]);
You can prepend $match stages as detailed in previous answers.
NOTE: Coming up in v5.4 (available now in 5.2 rapid release for MongoDB Atlas) is the long-awaited sortArray operator. If the dayWiseClicks was not in date order, then making it that way and finding the last 3 dates is freshingly simple:
db.foo.aggregate([
{$addFields: {dayWiseClicks: {$slice: [{$sortArray:{input: "$dayWiseClicks", sortBy: {"date":1}}, -3]}}}
]);
Similarly, to get the last 3 dates but in descending order:
db.foo.aggregate([
{$addFields: {dayWiseClicks: {$slice: [{$sortArray:{input: "$dayWiseClicks", sortBy: {"date":-1}}, 3]}}}
]);
For example, I have the following two docs
[
{
"_id": "5fc534505144dd0030c44f8e",
"createdAt": "2020-12-14T15:11:21.327Z"
"user_id": "2",
},
{
"_id": "5fc534505144dd0030c44f8e",
"createdAt": "2020-12-14T14:10:40.427Z",
"user_id": "1"
},
{
"_id": "5fc534595144dd0030c44f95",
"createdAt": "2020-12-13T14:10:58.027Z",
"user_id": "1"
}
]
the results should be
[
{
"date": "2020-12-13",
"count":1
},
{
"date": "2020-12-14",
"count":2
}
]
where the count is the number of distinct docs via user_ids till the date that specific cut off date
given data
data=[
{
"createdAt": "2020-12-14T15:11:21.327Z",
"user_id": "2",
},
{
"createdAt": "2020-12-14T14:10:40.427Z",
"user_id": "1"
},
{
"createdAt": "2020-12-13T14:10:58.027Z",
"user_id": "1"
},{
"createdAt": new Date("2020-12-14T14:10:58.027Z"),
}
]
> db.dummy.insert(data)
You may use aggregate: use $group with _id being the date's day in conjunction with $sum)
> db.dummy.aggregate({$group:{_id:{$substr:['$createdAt', 0, 10]}, count:{$sum:1}}})
{ "_id" : "2020-12-14", "count" : 3 }
{ "_id" : "2020-12-13", "count" : 1 }
edit: mongoose wise same may hold
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/dummy')
const UDate = mongoose.model('X', { createdAt:String, user_id: String }, 'dummy')
;(async()=>{
mongoose.set('debug', true)
const group = {$group:{_id:{$substr:['$createdAt', 0, 10]}, count:{$sum:1}}}
const v = await UDate.aggregate([group])
console.log('s : ', JSON.stringify(v))
mongoose.disconnect()
})()
edit2: to handle unicity of userIds so there are not counted twice per date, you may use $addToSet instead of sum followed by a projection using $size
const group = {$group:{_id:{$substr:['$createdAt', 0, 10]}, userIds:{$addToSet:'$user_id'}}}
const count = {$project:{date:'$_id', count: {$size: '$userIds'}} }
const v = await Stock.aggregate([group, count])
Lastly, if you feel always more, you can "rename" the _id field as date during the projection
{$project:{date:'$_id', _id:0, count: {$size: '$userIds'}} }
$gorup by createdAt date after getting substring using $substr and make array of unique user ids on the base of $addToset
get total element in count array using $size
db.collection.aggregate([
{
$group: {
_id: { $substr: ["$createdAt", 0, 10] },
count: { $addToSet: "$user_id" }
}
},
{ $addFields: { count: { $size: "$count" } } }
])
Playground
I have the following documents in my collection:
{
"_id": ObjectId("5b8fed64b77d7829788ebdc8"),
"valueId": "6e01c881-c15e-b754-43bd-0fe7381cc02a",
"value": 14,
"date": "2018-09-05T14:51:11.427Z"
}
I want to group the "date" by a certain interval, get for all "valueId" a sum of the "value", which is inside the date interval. My current aggregation looks like this:
myCollection.aggregate([
{
$match: {
date: {
$gte: start,
$lte: end,
},
},
},
{
$group: {
_id: {
$toDate: {
$subtract: [{ $toLong: '$date' }, { $mod: [{ $toLong: '$date' }, interval] }],
},
},
valueId: { $addToSet: '$valueId' },
},
},
{
$project: {
_id: 1,
valueId: 1,
},
},
]);
Which gives out something like this:
{
_id: 2018-09-04T15:45:00.000Z,
valueId:[
'cb255343-9c16-f495-9c29-3697d6c7d6cb',
'97e729aa-7b0f-c107-d591-01188b768a7a'
]
}
How can I get it to something like this (simplified with one value):
{
_id: 2018-09-04T15:45:00.000Z,
valueId: [[
'cb255343-9c16-f495-9c29-3697d6c7d6cb',
<sum of value>
]]
}
EDIT:
Endsolution:
myCollection.aggregate([
{"$match":{"date":{"$gte":start,"$lte":end}}},
{"$group":{
"_id":{
"interval":{"$toDate":{"$subtract":[{"$toLong":"$date"},{"$mod":[{"$toLong":"$date"},interval]}]}},
"valueId":"$valueId"
},
"value":{"$sum":"$value"}
}},
{ $group: {
_id: "$_id.interval",
values: {
$addToSet: { id: "$_id.valueId", sum: "$value" },}
}}])
You can use multiple group, one for summing value for each valueid and interval combination and second group to push all the documents for interval.
myCollection.aggregate([
{"$match":{"date":{"$gte":start,"$lte":end}}},
{"$group":{
"_id":{
"interval":{"$toDate":{"$subtract":[{"$toLong":"$date"},{"$mod":[{"$toLong":"$date"},interval]}]}},
"valueId":"$valueId"
},
"value":{"$sum":"$value"}
}},
{"$group":{
"_id":"$_id.interval",
"valueId":{"$push":{"id":"$_id.valueId","sum":"$value"}}
}}
])