MongoDB搜索和排序,具有匹配数和完全匹配

Raj*_*ale 1 regex sorting mongodb

我想创建一个小的MongoDB搜索查询,我想根据完全匹配对结果集进行排序,然后是否.比赛

例如.如果我有以下标签

Physics
11th-Physics
JEE-IIT-Physics
Physics-Physics
Run Code Online (Sandbox Code Playgroud)

然后,如果我搜索"物理",它应该排序为

Physics
Physics-Physics
11th-Physics
JEE-IIT-Physics
Run Code Online (Sandbox Code Playgroud)

Bla*_*ven 6

寻找你在这里谈论的那种"得分"是"不完美的解决方案"的练习.在这种情况下,"最佳拟合"在此处以"文本搜索"开头,而"不完美"是在使用MongoDB的文本搜索功能时首先考虑的术语.

MongoDB"不是"专用的"文本搜索"产品,也不像大多数数据库那样试图成为一个产品."文本搜索"的完整功能保留给专门的产品,这些产品可以作为专业领域.所以也许不是最合适的,但"文本搜索"是为那些能够忍受限制并且不想实现其他引擎的人提供的选项.或者还是!至少.

话虽如此,让我们看一下你可以用给定的数据样本做些什么.首先在集合中设置一些数据:

db.junk.insert([
    { "data": "Physics" },
    { "data": "11th-Physics" },
    { "data": "JEE-IIT-Physics" },
    { "data": "Physics-Physics" },
    { "data": "Something Unrelated" }
])
Run Code Online (Sandbox Code Playgroud)

然后当然要"启用"文本搜索功能,那么您需要使用"text"索引类型索引文档中的至少一个字段:

db.junk.createIndex({ "data": "text" })
Run Code Online (Sandbox Code Playgroud)

现在已经"准备好了",让我们来看看第一个基本查询:

db.junk.find(
   { "$text": { "$search": "\"Physics\"" } },
   { "score": { "$meta": "textScore" } }
).sort({ "score": { "$meta": "textScore" } })
Run Code Online (Sandbox Code Playgroud)

这将得到这样的结果:

{
    "_id" : ObjectId("55af83b964876554be823f33"),
    "data" : "Physics-Physics",
    "score" : 1.5
}
{
    "_id" : ObjectId("55af83b964876554be823f30"),
    "data" : "Physics",
    "score" : 1
}
{
    "_id" : ObjectId("55af83b964876554be823f31"),
    "data" : "11th-Physics",
    "score" : 0.75
}
{
    "_id" : ObjectId("55af83b964876554be823f32"),
    "data" : "JEE-IIT-Physics",
    "score" : 0.6666666666666666
}
Run Code Online (Sandbox Code Playgroud)

因此,它"接近"您想要的结果,但当然没有"完全匹配"组件.此外,文本搜索功能与$text操作符一起使用的逻辑意味着"物理 - 物理"是这里的首选匹配.

这是因为引擎不识别"非单词",例如中间的"连字符".对于它,"物理"一词在文档的索引内容中出现了几次,因此它具有更高的分数.

现在,你的其余逻辑取决于"完全匹配"的应用以及你的意思.如果您在字符串中查找"Physics",并且"不是"由"连字符"或其他字符包围,则以下内容不适用.但你可以匹配一个"完全"只是"物理"的字段"值":

db.junk.aggregate([
    { "$match": { 
        "$text": { "$search": "Physics" } 
    }},
    { "$project": {
        "data": 1,
        "score": {
           "$add": [
               { "$meta": "textScore" },
               { "$cond": [
                   { "$eq": [ "$data", "Physics" ] },
                   10,
                   0
               ]}
           ]
        }
    }},
    { "$sort": { "score": -1 } }
])
Run Code Online (Sandbox Code Playgroud)

这将给你一个结果,既可以查看引擎生成的"textScore",然后通过逻辑测试应用一些数学运算.在这种"数据"完全等于"物理"的情况下,我们使用$add以下因素将分数"加权"一个额外的因子:

{
    "_id": ObjectId("55af83b964876554be823f30"),
    "data" : "Physics",
    "score" : 11
}
{
    "_id" : ObjectId("55af83b964876554be823f33"),
    "data" : "Physics-Physics",
    "score" : 1.5
}
{
    "_id" : ObjectId("55af83b964876554be823f31"),
    "data" : "11th-Physics",
    "score" : 0.75
}
{
    "_id" : ObjectId("55af83b964876554be823f32"),
    "data" : "JEE-IIT-Physics",
    "score" : 0.6666666666666666
}
Run Code Online (Sandbox Code Playgroud)

这就是聚合框架可以为您做的事情,允许通过附加条件操纵返回的数据.最终结果传递给$sort阶段(注意它按降序颠倒)以允许新值为排序键.

但是聚合框架实际上只能处理字符串上的"完全匹配".目前没有工具来处理正则表达式匹配或字符串中的索引位置,这些位置返回有意义的投影值.甚至不是逻辑匹配.该$regex操作仅用于在查询中"过滤",因此不在此处使用.

因此,如果您在"短语"中寻找的东西比"字符串等于"精确匹配更多,那么另一个选项是使用mapReduce.

这是另一种"不完美"方法,因为mapReduce命令的限制意味着引擎的这种查询的"textScore""完全消失".虽然将正确选择实际文档,但引擎无法使用继承的"排名数据".这是MongoDB首先将"得分""投射"到文档中的副产品,而"投影"不是可用的功能mapReduce.

但你可以使用JavaScript"玩"字符串,就像在我的"不完美"样本中一样:

db.junk.mapReduce(
    function() {
        var _id = this._id,
            score = 0;

        delete this._id;

        score += this.data.indexOf(search);
        score += this.data.lastIndexOf(search);

        emit({ "score": score, "id": _id }, this);
    },
    function() {},
    { 
        "out": { "inline": 1 },
        "query": { "$text": { "$search": "Physics" } },
        "scope": { "search": "Physics" }
    }
)
Run Code Online (Sandbox Code Playgroud)

这给出了这样的结果:

{
    "_id" : {
        "score" : 0,
        "id" : ObjectId("55af83b964876554be823f30")
    },
    "value" : {
        "data" : "Physics"
    }
},
{
    "_id" : {
        "score" : 8,
        "id" : ObjectId("55af83b964876554be823f33")
    },
    "value" : {
        "data" : "Physics-Physics"
    }
},
{
    "_id" : {
        "score" : 10,
        "id" : ObjectId("55af83b964876554be823f31")
    },
    "value" : {
        "data" : "11th-Physics"
    }
},
{
    "_id" : {
        "score" : 16,
        "id" : ObjectId("55af83b964876554be823f32")
    },
    "value" : {
        "data" : "JEE-IIT-Physics"
   }
}
Run Code Online (Sandbox Code Playgroud)

我自己的"愚蠢的小算法"在这里基本上同时采用匹配字符串的"第一"和"最后"索引位置并将它们加在一起以产生分数.这可能不是你真正想要的,但关键是如果你可以用JavaScript编写逻辑代码,那么你可以把它扔到引擎上以产生所需的"排名".

这里要记住的唯一真正"技巧"是"得分" 必须是此处"分组"分组的"前置"部分,并且如果包含原始文档_id值,则必须重命名该复合键部分,否则_id意志优先于订单.

这只是mapReduce"优化"所有输出"关键"值在由reducer处理之前以"升序"排序的部分.这当然没有什么,因为我们不是"聚合",而是仅仅使用JavaScript运行器和文档重新整形mapReduce.


总的来说,这些是可用的选项.它们都不是完美的,但你可以和它们一起生活,甚至只是"接受"默认的引擎结果.

如果你想要更多,那么看看外部"专用"文本搜索产品,这将更适合.


附注:$text此处的搜索是首选,$regex因为它们可以使用索引."非锚定"正则表达式(没有插入符号^)不能与MongoDB最佳地使用索引.因此,$text搜索通常将是在短语中找到"单词"的更好基础.