在mongoDB中分页搜索结果

时间:2014-05-24 07:23:56

标签: node.js mongodb aggregation-framework

我正在尝试在下面的mongoDB中对我的搜索结果进行分页

{
"data": [
{
  "_id": "538037b869a1ca1c1ffc96e3",
  "jobs": "america movie"
},
{
  "_id": "538037a169a1ca1c1ffc96e0",
  "jobs": "superman movie"
},
{
  "_id": "538037a769a1ca1c1ffc96e1",
  "jobs": "spider man movie"
},
{
  "_id": "538037af69a1ca1c1ffc96e2",
  "jobs": "iron man movie"
},
{
  "_id": "538037c569a1ca1c1ffc96e4",
  "jobs": "social network movie"
}
],
 "Total_results": 5,
 "author": "Solomon David"
 }

由textScore索引和排序,所以我实现了如下所示的分页

app.get('/search/:q/limit/:lim/skip/:skip',function(req,res){

var l = parseInt(req.params.lim);
var s = parseInt(req.params.skip);
db.jobs.aggregate({$match:{$text:{$search:req.params.q}}},
{$sort:{score:{$meta:"textScore"}}},{$skip:s},{$limit:l},function(err,docs){res.send({data:docs,Total_results:docs.length,author:"Solomon David"});});

});

但是当我尝试这样的时候 localhost:3000 / search / movie / limit / 1 / skip / 0  我将结果限制为1并跳过没有,所以我必须得到如下结果。

{
  "data": [
    {
      "_id": "538037b869a1ca1c1ffc96e3",
      "jobs": "america movie"
    }
]}

但我会这样

{
  "data": [
    {
      "_id": "538037a169a1ca1c1ffc96e0",
      "jobs": "superman movie"
    }
  ],
  "Total_results": 1,
  "author": "Solomon David"
}

请帮助我,我做错了什么

1 个答案:

答案 0 :(得分:2)

这里似乎有一些事情要解释,所以我会尝试依次介绍它们。但首先要解决的是您要呈现的文档结构。数组不会产生你想要的结果,所以这里有一个基本的集合结构,现在称它为“电影”:

{
    "_id" : "538037b869a1ca1c1ffc96e3",
    "jobs" : "america movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037a169a1ca1c1ffc96e0",
    "jobs" : "superman movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037a769a1ca1c1ffc96e1",
    "jobs" : "spider man movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037af69a1ca1c1ffc96e2",
    "jobs" : "iron man movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037c569a1ca1c1ffc96e4",
    "jobs" : "social network movie",
    "author" : "Solomon David"
}

因此,所有项目都在单独的文档中,每个文档都有自己的详细信息和“作者”键。现在让我们考虑基本的文本搜索语句,仍然使用聚合:

db.movies.aggregate([
    { "$match": {
        "$text": {
            "$search": "movie"
        }
    }},
    { "$sort": { "score": { "$meta": "textScore" } } }
])

这将搜索所提供术语的创建“文本”索引,并返回该查询中“textScore”排名的结果。这里使用的表格是这些阶段的简写,您可以使用它们来实际看到“得分”值:

    { "$project": {
        "jobs": 1,
        "author": 1,
        "score": { "$meta": "textScore" }
    }},
    { "$sort": { "score": 1 }}

但样本上产生的结果将是:

{
    "_id" : "538037a169a1ca1c1ffc96e0",
    "jobs" : "superman movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037b869a1ca1c1ffc96e3",
    "jobs" : "america movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037c569a1ca1c1ffc96e4",
    "jobs" : "social network movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037af69a1ca1c1ffc96e2",
    "jobs" : "iron man movie",
    "author" : "Solomon David"
}
{
    "_id" : "538037a769a1ca1c1ffc96e1",
    "jobs" : "spider man movie",
    "author" : "Solomon David"
}

实际上,所有内容都有相同的“textScore”,但这是MongoDB返回它们的顺序。除非您提供其他加权或其他排序字段,否则该订单不会更改。

这基本上涵盖了文本搜索意味着发生的第一部分。文本搜索不能修改顺序或过滤文档中包含的数组的内容,因此这就是文档分离的原因。

分析这些结果是一个简单的过程,即使 $skip $limit 不是最有效的方法,但是通常,在使用“文本搜索”时,您将没有太多其他选择。

你似乎想要实现的目标是以某种方式在你的结果中产生一些关于你的搜索的“统计数据”。无论如何,将数据存储在数组中的文档并不是解决这个问题的方法。所以首先要看的是一个综合聚合示例:

db.movies.aggregate([
    { "$match": {
        "$text": {
            "$search": "movie"
        }
    }},
    { "$sort": { "score": { "$meta": "textScore" } } },
    { "$group": {
        "_id": null,
        "data": {  
            "$push": {
                "_id": "$_id",
                "jobs": "$jobs",
                "author": "$author"
            }
        },
        "Total_Results": { "$sum": 1 },
        "author": { 
            "$push": "$author"
         }
    }},
    { "$unwind": "$author" },
    { "$group": {
        "_id": "$author",
        "data": { "$first": "$data" },
        "Total_Results": { "$first": "$Total_Results" },
        "authorCount": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "data": { "$first": "$data" },
        "Total_Results": { "$first": "$Total_Results" },
        "Author_Info": {
            "$push": {
                "author": "$_id",
                "count": "$authorCount"
            }
        }
    }},        
    { "$unwind": "$data" },
    { "$skip": 0 },
    { "$limit": 2 },
    { "$group": {
        "_id": null,
        "data": { "$push": "$data" },
        "Total_Results": { "$first": "$Total_Results" },
        "Author_Info": { "$first": "$Author_Info" }
    }}
])

您在很多阶段看到的内容是,您在“Total_Results”和“Author_Info”中获得了一些关于总搜索结果的“统计信息”,并使用 $skip 和< strong> $limit 选择要返回的两个条目的“页面”:

{
    "_id" : null,
    "data" : [
            {
                    "_id" : "538037a169a1ca1c1ffc96e0",
                    "jobs" : "superman movie",
                    "author" : "Solomon David"
            },
            {
                    "_id" : "538037b869a1ca1c1ffc96e3",
                    "jobs" : "america movie",
                    "author" : "Solomon David"
            }
    ],
    "Total_Results" : 5,
    "Author_Info" : [
            {
                    "author" : "Solomon David",
                    "count" : 5
            }
    ]
}

这里的问题是,当你有大量的结果时,你会发现这将变得非常不实用。这里的关键部分是,为了获得这些“统计信息”,您需要将 $group 用于 $push 所有结果单个文档的数组。对于几百个结果或更多结果可能没问题,但是对于数千个结果会有显着的性能下降,更不用说内存资源的使用以及基本上打破单个文档的16MB BSON限制的真实可能性。

所以在聚合中做一切并不是最实用的解决方案,如果你真的需要“统计”,那么你最好的选择就是把它分成两个查询。首先是“统计”的汇总:

db.movies.aggregate([
    { "$match": {
          "$text": {
              "$search": "movie"
          }
    }},
    { "$group": {
        "_id": "$author",
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "Total_Results": { "$sum": "$count" },
        "Author_Info": {
            "$push": {
                "author": "$_id",
                "count": "$count"
            }
        }
    }}
])

这基本上是相同的,除了这次我们没有将“数据”与实际搜索结果一起存储而不担心分页,因为这是仅提供统计数据的结果的单个记录。它很快就会下降到单个记录,并且或多或少停留在那里,因此这是一个可扩展的解决方案。

显而易见的是,您不需要为每个“页面”执行此操作,只需要使用初始查询运行此操作。可以轻松缓存“统计信息”,以便您可以使用每个“页面”请求检索该数据。

现在要做的只是在没有“统计信息”的情况下运行每页所需结果的查询,这可以使用。find()形式完成:

db.movies.find(
    { "$text": { "$search": "movie" } },
    { "score": { "$meta": "textScore" } }
).sort({ "score": { "$meta": "textScore" } }).skip(0).limit(2)

这里的简短教训是,您希望从搜索中获取“统计数据”,并在单独的步骤中执行实际的结果分页。对于通用数据库分页而言,这是非常常见的做法,就像“总结果”的“统计”一样简单。

除此之外,其他选择是查看MongoDB外部的全文搜索解决方案。这些功能比MongoDB开箱即用的“水中脚趾”实现更具特色,并且还可能提供更好的性能解决方案,用于“分页”大量结果 $skip $limit 可以提供。