【问题标题】:Find documents based on Year in Mongodb在 Mongodb 中查找基于年份的文档
【发布时间】:2023-03-07 04:01:01
【问题描述】:

有一个名为 Movie 的模式,其中包含有关电影的信息。

电影模式

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
}

我需要从集合电影中的每个查询[Pagination]中选择10个不同条件的项目。我在我的API中添加了3个条件[基于通用名称、发布日期、语言]。

Js 代码

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);
        });
});

我需要再添加一个条件,条件是从集合 Movie 中选择具有年份集合 [例如:2003、2004、2010 等] 的发布日期 [m_release_date] 的项目。我该怎么做?enter code here

例子:

电影合集

[   
    {
        "_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 请求

【问题讨论】:

  • 最好的解决方案是在每个文档中添加一个year 字段,这样您就可以使用{$in: [ 2003, 2004, 2005, 2007]} 对其进行查询。否则,看看$year
  • 我不能对每个文档使用年份。因为数据来自第三方。我可以在没有聚合函数的情况下使用 $year 吗?

标签: javascript node.js mongodb mongoose mongodb-query


【解决方案1】:

以最高效的方式修复您的数据

老实说,最有效的方法是在您的数据中为m_release_year 创建一个新字段。然后将$in 条件提供给查询来代替日期范围就变成了一件简单的事情,但这当然可以使用索引。

所以有了这样一个字段,那么启动查询的代码就变成了:

// 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) {

在数据中放置新字段是在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 = [];
}

这将迭代集合中的所有项目并“提取”年份信息,然后写入新字段。然后创建一个与查询选择中使用的字段匹配的索引是明智的。

强制计算

否则,您基本上是在“强制计算”,并且没有数据库可以有效地做到这一点。 MongoDB 中的两种方法使用$where$redact,其中“后者”应始终优先于前者,因为至少$redact 使用本机编码操作进行比较,而不是JavaScript 评估$where,运行速度要慢得多。

// 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 })
      ) };
}

然后要么使用$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) => {

  }
)

或通过$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) {    

因为基本逻辑是从$year.getUTCFullYear()m_release_date 字段中提取当前年份,并将其用于与yearSelection 列表进行比较,以便仅返回匹配的那些。

对于$redact 的使用,对于最新版本(3.4 及更高版本),实际比较最有效的是通过$in 完成,或者使用$or 中的值,我们有效地将.map() 用于条件数组而不是而不是直接将数组作为参数应用。


结论

这里的一般建议是,如果您打算定期查询实际数据,则应将其包含在您的集合中。有了实际值,您可以在字段上放置索引,常规查询操作员可以使用这些值并利用索引。

如果不将“年份”的值放入集合中,则需要将后续“计算”应用于所有可能的条目,以确定哪个匹配项。所以效率不高。

即使在这个例子中,我们也试图通过至少抛出基于给定条目的日期的“可能范围”来“恢复”一些效率,假定从最小到最大。当然,在那个选择中也有“未使用的年份”,但总比什么都不提供,只是简单地在计算上选择要好。

【讨论】:

【解决方案2】:

我可以建议使用$where 运算符。

这里的主要思想是构造一个函数,该函数将适合您的参数数量及其值。 不精确,但接近的解决方案:

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);
    });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-10
    • 2017-02-27
    • 1970-01-01
    • 2018-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-25
    相关资源
    最近更新 更多