Reputation: 7954
There is a modal called Movie having information about a movie.
Movie Modal
var mongoose = require('mongoose');
var movieSchema = new mongoose.Schema({
m_tmdb_id: {
type: Number,
unique: true,
index: true
},
m_backdrop_path: {
type: String,
},
m_budget: {
type: Number,
},
m_homepage: {
type: String
},
m_imdb_id: {
type: String,
},
m_original_language: {
type: String
},
m_original_title: {
type: String
},
m_poster_path: {
type: String
},
m_poster_key: {
type: String
},
m_release_date: {
type: Date
},
m_revenue: {
type: Number
},
m_runtime: {
type: Number
},
m_title: {
type: String
},
m_genres: {
type: Array
},
created_at: {
type: Date
},
updated_at: {
type: Date,
default: Date.now
}
});
var MovieModel = mongoose.model('Movie', movieSchema);
module.exports = {
movie: MovieModel
}
I need to select 10 items in each query [Pagination] from the collection Movie with different conditions.I have added 3 condition in my API[Based on gener name, release date, language].
Js Code
router.post('/movies', function(req, res, next) {
var perPage = parseInt(req.query.limit);
var page = req.query.page;
var datefrom = new Date();
var dateto = new Date();
var generNames = req.body.generNames;
dateto.setMonth(dateto.getMonth() - 2);
var queryOptions = {
$and: [{
'm_release_date': {
$lte: datefrom,
$gte: dateto
}
}, {
"m_genres.name": {
$in: generNames
}
}, {
'm_original_language': 'en'
}, ]
};
Movie
.find(queryOptions)
.select('_id m_tmdb_id m_poster_path m_original_title')
.sort('-m_release_date')
.limit(perPage)
.skip(perPage * page)
.exec(function(err, movies) {
if (movies) {
return res.status(200).json(movies);
}
}).catch(function(error) {
return res.status(500).json(error);
});
});
I need to add one more condition ,the condition is select items from the collection Movie that having release date [m_release_date] from the set of years [ex: 2003,2004,2010 etc].How can i do this?enter code here
Example:
Movie Collection
[
{
"_id": "59420dff3d729440f200bccc",
"m_tmdb_id": 453651,
"m_original_title": "PIETRO",
"m_poster_path": "/3sTFUZorLGOU06A7P3XxjLVKKGD.jpg",
"m_release_date": "2017-07-14T00:00:00.000Z",
"m_runtime": 8,
"m_genres": [{
"id": 18,
"name": "Drama"
}]
},
{
"_id": "594602610772b119e788edab",
"m_tmdb_id": 425136,
"m_original_title": "Bad Dads",
"m_poster_path": null,
"m_release_date": "2017-07-14T00:00:00.000Z",
"m_runtime": 0,
"m_credits_cast": [],
"m_genres": [{
"id": 35,
"name": "Comedy"
}]
},
{
"_id": "59587747d282843883df755e",
"m_tmdb_id": 364733,
"m_original_title": "Blind",
"m_poster_path": "/cXyObe5aB63ueOndEXxXabgAvIi.jpg",
"m_release_date": "2017-07-14T00:00:00.000Z",
"m_runtime": 105,
"m_genres": [{
"id": 18,
"name": "Drama"
}]
},
{
"_id": "595d93f9c69ab66c4f48254f",
"m_tmdb_id": 308149,
"m_original_title": "The Beautiful Ones",
"m_poster_path": "/kjy1obH5Oy1IsjTViYVJDQufeZP.jpg",
"m_release_date": "2017-07-14T00:00:00.000Z",
"m_runtime": 94,
"m_genres": [{
"id": 18,
"name": "Drama"
}]
},
{
"_id": "59420de63d729440f200bcc7",
"m_tmdb_id": 460006,
"m_original_title": "Черная вода",
"m_poster_path": "/kpiLwx8MGGWgZMMHUnvydZkya0H.jpg",
"m_release_date": "2017-07-13T00:00:00.000Z",
"m_runtime": 0,
"m_genres": []
},
{
"_id": "594602390772b119e788eda3",
"m_tmdb_id": 281338,
"m_original_title": "War for the Planet of the Apes",
"m_poster_path": "/y52mjaCLoJJzxfcDDlksKDngiDx.jpg",
"m_release_date": "2017-07-13T00:00:00.000Z",
"m_runtime": 142,
"m_genres": [{
"id": 28,
"name": "Action"
}
]
}
]
API Request
Upvotes: 0
Views: 470
Reputation: 151122
Honestly the most performant way this is going to happen is by creating a new field in your data for m_release_year
. Then it becomes a simple matter of supplying the $in
condition to the query in place of the date range, but this can of course use an index.
So with such a field in place, then the code to initiate the query becomes:
// Just to simulate the request
const req = {
body: {
"generNames": ["Action"],
"selectedYear": ["2003,2004,2005,2017"]
}
}
// Your selectedYear input looks wrong. So correcting from a single string
// to an actual array of integers
function fixYearSelection(input) {
return [].concat.apply([],input.map(e => e.split(",") )).map(e => parseInt(e) ).sort()
}
// Outputs like this - [ 2003, 2004, 2005, 2017 ]
let yearSelection = fixYearSelection(req.body.selectedYear);
Movie.find({
"m_release_year": { "$in": yearSelection },
"m_genres.name": { "$in": req.body.generNames },
"m_original_language": "en"
})
.select('_id m_tmdb_id m_poster_path m_original_title')
.sort('-m_release_date')
.limit(perPage)
.skip(perPage * page)
.exec(function(err, movies) {
Placing the new field in the data is a simple matter to run in the mongo
shell:
let ops = [];
db.movies.find({ "m_release_year": { "$exists": false } }).forEach( doc => {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": { "$set": { "m_release_year": doc.m_release_date.getUTCFullYear() } }
});
if ( ops.length >= 1000 ) {
db.movies.bulkWrite(ops);
ops = [];
}
});
if ( ops.length > 0 ) {
db.movies.bulkWrite(ops);
ops = [];
}
Which would iterate all items in the collection and "extract" the year information and then write to the new field. It would be wise to then create an index that matched the fields used in the query selection.
Without that then you are basically "forcing a calculation" and no database can do that efficiently. The two methods in MongoDB are using $where
or $redact
, where the "latter" should always be used in preference to the former since at least $redact
is using native coded operations for comparison, as opposed to the JavaScript evaluation of $where
, which runs much slower.
// Just to simulate the request
const req = {
body: {
"generNames": ["Action"],
"selectedYear": ["2003,2004,2005,2017"]
}
}
// Your selectedYear input looks wrong. So correcting from a single string
// to an actual array of integers
function fixYearSelection(input) {
return [].concat.apply([],input.map(e => e.split(",") )).map(e => parseInt(e) ).sort()
}
// Outputs like this - [ 2003, 2004, 2005, 2017 ]
let yearSelection = fixYearSelection(req.body.selectedYear);
/*
* Not stored, so we try to "guestimate" the reasonable "range" to at
* least give some query condtion on the date and not search everything
*/
var startDate = new Date(0),
startDate = new Date(startDate.setUTCFullYear(yearSelection[0])),
endDate = new Date(0),
endDate = new Date(endDate.setUTCFullYear(yearSelection.slice(-1)[0]+1));
// Helper to switch our $redact "if" based on supported MongoDB
const version = "3.4";
function makeIfCondition() {
return ( version === "3.4" )
? { "$in": [ { "$year": "$m_release_date" }, yearSelection ] }
: { "$or": yearSelection.map(y =>
({ "$eq": [{ "$year": "$m_release_date" }, y })
) };
}
Then either using $redact
:
Movie.aggregate(
[
{ "$match": {
"m_release_date": {
"$gte": startDate, "$lt": endDate
},
"m_genres.name": { "$in": req.body.generNames },
"m_original_language": "en"
}},
{ "$redact": {
"$cond": {
"if": makeIfCondition(),
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$sort": { "m_release_date": -1 } },
{ "$project": {
"m_tmdb_id": 1,
"m_poster_path": 1,
"m_original_title": 1
}},
{ "$skip": perPage * page },
{ "$limit": perPage }
],
(err,movies) => {
}
)
Or via $where
:
Movie.find({
"m_release_date": {
"$gte": startDate, "$lt": endDate
},
"m_genres.name": { "$in": req.body.generNames },
"m_original_language": "en",
"$where": function() {
return yearSelection.indexOf(this.m_release_date.getUTCFullYear()) !== -1
}
})
.select('_id m_tmdb_id m_poster_path m_original_title')
.sort('-m_release_date')
.limit(perPage)
.skip(perPage * page)
.exec(function(err, movies) {
Being that the basic logic is to instead extract by $year
or .getUTCFullYear()
the present year from the m_release_date
field and use that for comparison to the list of yearSelection
in order to only return those that match.
For the usage of $redact
the actual comparison is most effectively done via $in
for most recent releases ( 3.4 and upwards ) or otherwise using values from $or
where we effectively .map()
onto the array of conditions rather than apply the array directly as a argument.
The general recommendation here is to instead include the actual data within your collection if you intend to regularly query on it. With actual values in place, you can place an index on the field and regular query operators can use those values as well as take advantage of the index.
Without putting the values for the "year" in the collection, the subsequent "calculation" needs to be applied to all possible entries in order to determine which match. So it's not as efficient.
Even in this example, we try to "gain back" some efficiency by at least throwing the "possible range" of dates based on the given entries, being presumed from smallest to largest. But of course there are "unused years" in that selection, but it's better than providing nothing and simply selecting on the calculation alone.
Upvotes: 1
Reputation: 3154
I can suggest using $where operator.
The main idea here is to construct a function, that will fit number of your arguments and their values. Not precise, but close solution:
const year1 = 2005;
const year2 = 2007;
const yearFinder = new Function('',`return new Date(this.m_release_date).getFullYear() === ${year1} || new Date(this.m_release_date).getFullYear() === ${year2}`);
Movie
.find(queryOptions)
.$where(yearFinder)
.select('_id m_tmdb_id m_poster_path m_original_title')
.sort('-m_release_date')
.limit(perPage)
.skip(perPage * page)
.exec(function(err, movies) {
if (movies) {
return res.status(200).json(movies);
}
}).catch(function(error) {
return res.status(500).json(error);
});
Upvotes: 0