MongoDB 学习笔记十一 ObjectId、MapReduce、全文检索、正则表达式
MongoDB ObjectId
ObjectId 是一个 12字节 BSON 类型数据,有以下格式:
- 前4个字节表示时间戳
- 接下来的3个字节是机器标识码
- 紧接的两个字节由进程 id 组成(PID)
- 最后三个字节是随机数。
MongoDB 中存储的文档必须有一个 “_id”键。这个键的值可以是任何类型的,默认是个 ObjectId 对象。
在一个集合里面,每个文档都有唯一的 “_id” 值,来确保集合里面每个文档都能被唯一标识。
MongoDB 采用 ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个服务器上同步自动增加主键值既费力还费时。
创建新的 ObjectId
使用以下代码生成的新的 ObjectId:
>newObjectId = ObjectId()
上面的语句返回以下唯一生成的 id:
ObjectId("5349b4ddd2781d08c09890f3")
你也可以使用生成的 id 来取代 MongoDB 自动生成的 ObjectId:
>myObjectId = ObjectId("5349b4ddd2781d08c09890f4")
创建文档的时间戳
由于 ObjectId 中存储了 4 个字节的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过 getTimestamp 函数来获取文档的创建时间:
>ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()
以上代码将返回 ISO 格式的文档创建时间:
ISODate("2014-04-12T21:49:17Z")
ObjectId 转换为字符串
在某些情况下,您可能需要将 ObjectId 转换为字符串格式。你可以使用下面的代码:
>new ObjectId().str
以上代码将返回 Guid 格式的字符串:
5349b4ddd2781d08c09890f3
MongoDB Map Reduce
Map-Reduce 是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。
MongoDB 提供的 Map-Reduce 非常灵活,对于大规模数据分析也相当实用。
MapReduce 命令
以下是 MapReduce 的基本语法:
>db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number
}
)
实用 MapReduce 要实现两个函数Map 函数和 Reduce 函数。Map 函数调用 emit(key,value),遍历 collection 中所有的记录,将 key 与 value 传递给 Reduce 函数进行处理
Map 函数 必须使用 emit(key,value)返回键值对。
参数说明:
- map:映射函数(生成键值对序列,作为 reduce 函数参数)。
- reduce:统计函数,reduce 函数的任务就是将 key-values 编程 key -value,也就是把 values 变成一个单一的值 value。
- out: 统计结果存放集合(不指定则使用临时集合,在客户端点开后自动删除)。
- query:一个筛选条件,只有满足条件的文档才会调用 map 函数。(query。limit。sort 可以随意组合)
- sort 和 limit 结合的 sort 排序参数(也就是在发往map函数前给文档排序),可以优化分组机制
- limit:发往 map函数的文档数量的上限(要是没有 limit,单独使用 sort 的用处不大)
以下实例在集合 orders 中查找 statis.A 的数据,并根据 cust_id 来分组,并计算 amount 的总和。
使用 MapReduce
考虑以下文档结构存储用户的文章,文档存储了用户的 user_name 华为文章的 status 字段:
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "runoob",
"status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "runoob",
"status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "runoob",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
现在,我们将在 posts 集合中使用 mapReduce 函数来选取已发布的文章(status.“active”),并通过 user_name 分组,计算每个用户的文章数:
>db.posts.mapReduce(
function() { emit(this.user_name,1); },
function(key, values) {return Array.sum(values)},
{
query:{status:"active"},
out:"post_total"
}
).find()
以上查询显示如下结果:
{ "_id" : "mark", "value" : 4 }
{ "_id" : "runoob", "value" : 1 }
用类似的方式,MapReduce 可以被用来构建大型的聚合查询。
Map 函数 和 Reduce 函数可以使用 JavaScript 来实现,使得 MapReduce 的使用非常灵活和强大。
MongoDB 全文检索
全文检索对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
这个过程类似于通过字典中的检索字表查字的过程。
MongoDB 从 2。4 版本开始支持全文检索,目前支持 15 种语言的全文检索。
danish
dutch
english
finnish
french
german
hungarian
italian
norwegian
portuguese
romanian
russian
spanish
swedish
turkish
启用全文检索
MongoDB 在 2.6 版本以后是默认开启全文检索的,如果你使用之前的版本,你需要使用以下代码来启用全文检索
>db.adminCommand({setParameter:true,textSearchEnabled:true})
或者使用命令:
mongod --setParameter textSearchEnabled=true
创建全文索引
考虑以下 posts 集合的文档数据,包含了文章内容(post_text)及标签(tags):
{
"post_text": "enjoy the mongodb articles on Runoob",
"tags": [
"mongodb",
"runoob"
]
}
我们可以对 post_text 字段建立全文索引,这样我们可以搜索文章内的内容:
>db.posts.ensureIndex({post_text:"text"})
使用全文索引
现在我们已经对 post_text 建立了全文索引,我们可以搜索文章中的关键词 runoob:
>db.posts.find({$text:{$search:"runoob"}})
以下命令返回了如下包含 runoob 关键词的文档数据:
{
"_id" : ObjectId("53493d14d852429c10000002"),
"post_text" : "enjoy the mongodb articles on Runoob",
"tags" : [ "mongodb", "runoob" ]
}
如果你使用的是旧版本的 MongoDB ,你可以使用以下命令:
>db.posts.runCommand("text",{search:"runoob"})
使用全文索引可以提高搜索效率。
删除全文索引
删除已存在的全文索引,可以使用 find 命令查找索引名:
>db.posts.getIndexes()
通过以上命令获取索引名,本例的索引名为 post_text_text,执行以下命令过来删除索引:
>db.posts.dropIndex("post_text_text")
MongoDB 正则表达式
正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。
许多程序设计语言都支持利用正则表达式进行字符串操作。
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。
MongoDB 使用 PCRE(Perl Compatible Regular Expression)作为正则表达式语言。
不同于全文检索,我们只用正则表达式不需要做任何配置。
考虑以下 posts 集合的文档结构,该文档包含了文章内容和标签:
{
"post_text": "enjoy the mongodb articles on runoob",
"tags": [
"mongodb",
"runoob"
]
}
使用正则表达式
以下命令使用正则表达式查找包含 runoob 字符串的文章:
>db.posts.find({post_text:{$regex:"runoob"}})
以上查询也可以写为:
>db.posts.find({post_text:/runoob/})
不区分大小写的正则表达式
如果检索需要不区分大小写,我们可以设置 $options 为 $i 。
以下命令将查找不区分大小写的字符串 runoob :
>db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})
集合中会返回所有包含字符串 runoob 的数据,且不区分大小写:
{
"_id" : ObjectId("53493d37d852429c10000004"),
"post_text" : "hey! this is my post on runoob",
"tags" : [ "runoob" ]
}
数组元素使用正则表达式
我们还可以在数组字段中使用正则表达式来查找内容。这在标签的实现上非常有用,如果你需要查找包含 以 run 开头的标签数据(ru 或 run 或 runoob),你可以使用以下代码:
>db.posts.find({tags:{$regex:"run"}})
优化正则表达式查询
- 如果你的文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找所有的数据查询速度更快。
- 如果正则表达式是前缀表达式,所有匹配的数据将以指定的前缀字符串为开始。例如:如果正则表达式为 ^tut,查询语句将查找 以 tut 为开头的字符串。
这里面使用正则表达式有两点需要注意:
正则表达式中使用变量。一定要使用 eval 将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。否则没有报错信息,只是结果为空!
实例如下:
var name=eval("/" + 变量值key +"/i");
以下是模糊查询包含 title 关键词,且不区分大小写:
title:eval("/"+title+"/i") // 等同于 title:{$regex:title,$Option:"$i"}
regex 操作符的介绍
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式,使用PCRE(Pert Compatible Regular Expression)作为正则表达式语言。
regex操作符:
{<field>:{$regex:/pattern/,$options:’<options>’}}
{<field>:{$regex:’pattern’,$options:’<options>’}}
{<field>:{$regex:/pattern/<options>}}
正则表达式对象
{<field>: /pattern/<options>}
$regex与正则表达式对象的区别:
- 在$in操作符中只能使用正则表达式对象,例如:{name:{$in:[/^joe/i,/^jack/]}}
- 在使用隐式的$and操作符中,只能使用$regex,例如:{name:{$regex:/^jo/i, $nin:['john']}}
- 当option选项中包含X或S选项时,只能使用$regex,例如:{name:{$regex:/m.*line/,$options:"si"}}
$ regex 操作符的使用
$regex操作符中的option选项可以改变正则匹配的默认行为,它包括i, m, x以及S四个选项,其含义如下
i 忽略大小写,{<field>{$regex/pattern/i}},设置i选项后,模式中的字母会进行大小写不敏感匹配。
m 多行匹配模式,{<field>{$regex/pattern/,$options:'m'},m选项会更改^和$元字符的默认行为,分别使用与行的开头和结尾匹配,而不是与输入字符串的开头和结尾匹配。
x 忽略非转义的空白字符,{<field>:{$regex:/pattern/,$options:'m'},设置x选项后,正则表达式中的非转义的空白字符将被忽略,同时井号(#)被解释为注释的开头注,只能显式位于option选项中。
s 单行匹配模式{<field>:{$regex:/pattern/,$options:'s'},设置s选项后,会改变模式中的点号(.)元字符的默认行为,它会匹配所有字符,包括换行符(\n),只能显式位于option选项中。
使用 $regex 操作符时,需要注意下面几个问题:
i,m,x,s可以组合使用,例如:{name:{$regex:/j*k/,$options:"si"}}
在设置索弓}的字段上进行正则匹配可以提高查询速度,而且当正则表达式使用的是前缀表达式时,查询速度会进一步提高,例如:{name:{$regex: /^joe/}