Node.js と mongoose を使用して webapp を作成しています。.find()
呼び出しから得た結果をページ分割するにはどうすればよいですか? "LIMIT 50,100"
SQLに匹敵する機能が欲しい。
32 に答える
この質問で受け入れられた回答に非常に失望しています。これはスケーリングしません。cursor.skip( ) の詳細を読むと、次のようになります。
cursor.skip() メソッドは、サーバーがコレクションまたはインデックスの先頭から移動してオフセットを取得するか、結果を返し始める前に位置をスキップする必要があるため、多くの場合コストがかかります。オフセット (上記の pageNumber など) が増加すると、cursor.skip() は遅くなり、CPU の負荷が高くなります。大きなコレクションでは、cursor.skip() が IO バウンドになることがあります。
スケーラブルな方法でページネーションを実現するには、 limit( ) と少なくとも 1 つのフィルター条件を組み合わせます。createdOn の日付は多くの目的に適しています。
MyModel.find( { createdOn: { $lte: request.createdOnBefore } } )
.limit( 10 )
.sort( '-createdOn' )
Rodolphe から提供された情報を使用して Mongoose API を詳しく調べた後、次の解決策を見つけました。
MyModel.find(query, fields, { skip: 10, limit: 5 }, function(err, results) { ... });
マングース、エクスプレス、ジェイドを使用したページネーション -詳細については、私のブログへのリンクを参照してください。
var perPage = 10
, page = Math.max(0, req.params.page)
Event.find()
.select('name')
.limit(perPage)
.skip(perPage * page)
.sort({
name: 'asc'
})
.exec(function(err, events) {
Event.count().exec(function(err, count) {
res.render('events', {
events: events,
page: page,
pages: count / perPage
})
})
})
次のようにチェーンできます。
var query = Model.find().sort('mykey', 1).skip(2).limit(5)
を使用してクエリを実行しますexec
query.exec(callback);
これを簡単にするMongoose Paginateという小さなパッケージを使用できます。
$ npm install mongoose-paginate
ルートまたはコントローラーの後に、次を追加するだけです:
/**
* querying for `all` {} items in `MyModel`
* paginating by second page, 10 items per page (10 results, page 2)
**/
MyModel.paginate({}, 2, 10, function(error, pageCount, paginatedResults) {
if (error) {
console.error(error);
} else {
console.log('Pages:', pageCount);
console.log(paginatedResults);
}
}
これは、これを試すことができるサンプル例です。
var _pageNumber = 2,
_pageSize = 50;
Student.count({},function(err,count){
Student.find({}, null, {
sort: {
Name: 1
}
}).skip(_pageNumber > 0 ? ((_pageNumber - 1) * _pageSize) : 0).limit(_pageSize).exec(function(err, docs) {
if (err)
res.json(err);
else
res.json({
"TotalCount": count,
"_Array": docs
});
});
});
これは私がコードでそれをしたことです
var paginate = 20;
var page = pageNumber;
MySchema.find({}).sort('mykey', 1).skip((pageNumber-1)*paginate).limit(paginate)
.exec(function(err, result) {
// Write some stuff here
});
それが私がやった方法です。
これは、すべてのモデルに取り付けるバージョンです。利便性のためにアンダースコアに依存し、パフォーマンスのために非同期に依存します。opts により、マングース構文を使用したフィールドの選択と並べ替えが可能になります。
var _ = require('underscore');
var async = require('async');
function findPaginated(filter, opts, cb) {
var defaults = {skip : 0, limit : 10};
opts = _.extend({}, defaults, opts);
filter = _.extend({}, filter);
var cntQry = this.find(filter);
var qry = this.find(filter);
if (opts.sort) {
qry = qry.sort(opts.sort);
}
if (opts.fields) {
qry = qry.select(opts.fields);
}
qry = qry.limit(opts.limit).skip(opts.skip);
async.parallel(
[
function (cb) {
cntQry.count(cb);
},
function (cb) {
qry.exec(cb);
}
],
function (err, results) {
if (err) return cb(err);
var count = 0, ret = [];
_.each(results, function (r) {
if (typeof(r) == 'number') {
count = r;
} else if (typeof(r) != 'number') {
ret = r;
}
});
cb(null, {totalCount : count, results : ret});
}
);
return qry;
}
モデル スキーマにアタッチします。
MySchema.statics.findPaginated = findPaginated;
これを実装する確実な方法は、クエリ文字列を使用してフロントエンドから値を渡すことです。ページ #2 を取得し、出力を25 resultsに制限するとします。
クエリ文字列は次のようになります。?page=2&limit=25 // this would be added onto your URL: http:localhost:5000?page=2&limit=25
コードを見てみましょう:
// We would receive the values with req.query.<<valueName>> => e.g. req.query.page
// Since it would be a String we need to convert it to a Number in order to do our
// necessary calculations. Let's do it using the parseInt() method and let's also provide some default values:
const page = parseInt(req.query.page, 10) || 1; // getting the 'page' value
const limit = parseInt(req.query.limit, 10) || 25; // getting the 'limit' value
const startIndex = (page - 1) * limit; // this is how we would calculate the start index aka the SKIP value
const endIndex = page * limit; // this is how we would calculate the end index
// We also need the 'total' and we can get it easily using the Mongoose built-in **countDocuments** method
const total = await <<modelName>>.countDocuments();
// skip() will return a certain number of results after a certain number of documents.
// limit() is used to specify the maximum number of results to be returned.
// Let's assume that both are set (if that's not the case, the default value will be used for)
query = query.skip(startIndex).limit(limit);
// Executing the query
const results = await query;
// Pagination result
// Let's now prepare an object for the frontend
const pagination = {};
// If the endIndex is smaller than the total number of documents, we have a next page
if (endIndex < total) {
pagination.next = {
page: page + 1,
limit
};
}
// If the startIndex is greater than 0, we have a previous page
if (startIndex > 0) {
pagination.prev = {
page: page - 1,
limit
};
}
// Implementing some final touches and making a successful response (Express.js)
const advancedResults = {
success: true,
count: results.length,
pagination,
data: results
}
// That's it. All we have to do now is send the `results` to the frontend.
res.status(200).json(advancedResults);
このロジックをミドルウェアに実装して、さまざまなルート/コントローラーに使用できるようにすることをお勧めします。
最も簡単で迅速な方法は、objectId の例でページ付けすることです。
初期負荷条件
condition = {limit:12, type:""};
応答データから最初と最後の ObjectId を取得します
ページの次の条件
condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c662d", lastId:"57762a4c875adce3c38c6615"};
ページの次の条件
condition = {limit:12, type:"next", firstId:"57762a4c875adce3c38c6645", lastId:"57762a4c875adce3c38c6675"};
マングースでは
var condition = {};
var sort = { _id: 1 };
if (req.body.type == "next") {
condition._id = { $gt: req.body.lastId };
} else if (req.body.type == "prev") {
sort = { _id: -1 };
condition._id = { $lt: req.body.firstId };
}
var query = Model.find(condition, {}, { sort: sort }).limit(req.body.limit);
query.exec(function(err, properties) {
return res.json({ "result": result);
});
これは、ページネーションと制限オプションを使用してスキル モデルの結果を取得する関数の例です。
export function get_skills(req, res){
console.log('get_skills');
var page = req.body.page; // 1 or 2
var size = req.body.size; // 5 or 10 per page
var query = {};
if(page < 0 || page === 0)
{
result = {'status': 401,'message':'invalid page number,should start with 1'};
return res.json(result);
}
query.skip = size * (page - 1)
query.limit = size
Skills.count({},function(err1,tot_count){ //to get the total count of skills
if(err1)
{
res.json({
status: 401,
message:'something went wrong!',
err: err,
})
}
else
{
Skills.find({},{},query).sort({'name':1}).exec(function(err,skill_doc){
if(!err)
{
res.json({
status: 200,
message:'Skills list',
data: data,
tot_count: tot_count,
})
}
else
{
res.json({
status: 401,
message: 'something went wrong',
err: err
})
}
}) //Skills.find end
}
});//Skills.count end
}
let page,limit,skip,lastPage, query;
page = req.params.page *1 || 1; //This is the page,fetch from the server
limit = req.params.limit * 1 || 1; // This is the limit ,it also fetch from the server
skip = (page - 1) * limit; // Number of skip document
lastPage = page * limit; //last index
counts = await userModel.countDocuments() //Number of document in the collection
query = query.skip(skip).limit(limit) //current page
const paginate = {}
//For previous page
if(skip > 0) {
paginate.prev = {
page: page - 1,
limit: limit
}
//For next page
if(lastPage < counts) {
paginate.next = {
page: page + 1,
limit: limit
}
results = await query //Here is the final results of the query.
skip() と limit() のいずれかを使用できますが、非常に非効率的です。より良い解決策は、インデックス付きフィールドと limit() の並べ替えです。Wunderflats では、ここで小さなライブラリを公開しています: https://github.com/wunderflats/goosepage 最初の方法を使用します。
このようにクエリを書くことができます。
mySchema.find().skip((page-1)*per_page).limit(per_page).exec(function(err, articles) {
if (err) {
return res.status(400).send({
message: err
});
} else {
res.json(articles);
}
});
page : リクエスト パラメータとしてクライアントから送信されるページ番号。
per_page : ページごとに表示される結果の数
MEAN スタックを使用している場合、次のブログ投稿では、angular-UI ブートストラップを使用してフロントエンドでページネーションを作成し、バックエンドで mongoose のスキップおよび制限メソッドを使用するための多くの情報を提供しています。
参照: https://techpituwa.wordpress.com/2015/06/06/mean-js-pagination-with-angular-ui-bootstrap/
async/await でも結果を出すことができました。
hapi v17 および mongoose v5 で非同期ハンドラーを使用する以下のコード例
{
method: 'GET',
path: '/api/v1/paintings',
config: {
description: 'Get all the paintings',
tags: ['api', 'v1', 'all paintings']
},
handler: async (request, reply) => {
/*
* Grab the querystring parameters
* page and limit to handle our pagination
*/
var pageOptions = {
page: parseInt(request.query.page) - 1 || 0,
limit: parseInt(request.query.limit) || 10
}
/*
* Apply our sort and limit
*/
try {
return await Painting.find()
.sort({dateCreated: 1, dateModified: -1})
.skip(pageOptions.page * pageOptions.limit)
.limit(pageOptions.limit)
.exec();
} catch(err) {
return err;
}
}
}