MongoDb基本操作
1.keyvalue数据类型
上面说过,MongoDB的基本数据是以key-value为单位的,key只能是字符串,但value的数据类型则多种多样,这些类型基本从BSON格式的基础上进行一些添加和修改而来,以下是一些常见类型。
- /* 空值 null */
- { “name” : null }
- /* 布尔值 boolean */
- { “flag” : true }
- /* 数值
- 包括32bit、64bit整数integer类型和64bit浮点floating point类型 */
- {
- “copies” : 300,
- “price” : 60.8
- }
- /* 字符串 string */
- { “dbname” : “MongoDB” }
- /* 日期 date */
- { “post_time” : new Date() }
- /* 正则表达式 regular expression */
- { “name_match” : /huangz/i }
- /* 数组类型 array */
- { “tags” : [“mongodb”, “nosql”] }
- /* 嵌套文档 embedded document
- 一个文档里面Key的值可以是另外一个文档 */
- {
- “name” : “huangz”,
- “phone” : { “home” : 123321,
- “mobile” : 15820123123}
- }
- /* id
- 每个MongoDB文档都必须有一个叫作”_id”的Key, 同一集合里面的文档_id必须各不相同。
- id可以在创建文档的时候指定,可以是任何类型,无指定时,默认使用ObjectId对象,自动生成不同的id。
- ObjectId对象生成id(12位)的规则如下:
- 0-3 : Timestamp,时间撮
- 4-6 : Machine,机器代码
- 7-8 : PID,MongoDB的程序id
- 9-11: Increment,一个自增量
- */
- { “_id” : ObjectId(“4da025ac5149e6d915098c59”), “name” : “huangz”, “phone” : { “home” : 33123123, “mobile” : 15820123123 } }
不必担心自动生成id会重复,因为它足够大。
如果想模仿关系数据库,生成一个连续自增的id,可以使用类似如下的代码来实现:
- /* 生成连续自增id */
- > db.person.id = 0
- 0
- > db.person.insert({
- … “_id” : db.person.id += 1,
- … “name” : “huangz”
- … })
- > db.person.insert({
- … “_id” : db.person.id += 1,
- … “name” : “jack”,
- … })
- > db.person.find()
- { “_id” : 1, “name” : “huangz” }
- { “_id” : 2, “name” : “jack” }
- > db.person.find({“_id” : 1})
- { “_id” : 1, “name” : “huangz” }
了解MongoDB的基本数据类型和组织方式后,我们可以测试以下常见的CRUD操作。
- /* 选择数据库和集合,这里使用test数据库 */
- > use test
- switched to db test
- /* CREATE 创建一个post文档,并将post添加到test.blog集合里面 */
- > post = {
- … “title” : “First day with MongoDB”,
- … “author” : “huangz”,
- … “content” : “Today, i try MongoDB, it\’s great!”,
- … “date” : new Date()
- … }
- {
- “title” : “First day with MongoDB”,
- “author” : “huangz”,
- “content” : “Today, i try MongoDB, it\’s great!”,
- “date” : “Sat Apr 09 2011 16:21:51 GMT+0800 (CST)”
- }
- > db.blog.insert(post) /*这里的db是一个指向test数据库的变量 */
- /* READ 查询创立的post文档,MongoDB提供了包括find和findOne等多种查询方式
- find函数的参数如果为空字典,则是查询整个集合,显示最先20条记录(record),
- find函数也可以使用参数,作为查询条件,
- findOne函数和find类似,但是只返回符合条件的第一条记录 */
- > db.blog.find()
- { “_id” : ObjectId(“4da017c55149e6d915098c57”), “title” : “First day with MongoDB”, “author” : “huangz”, “content” : “Today, i try MongoDB, it\’s great!”, “date” : “Sat Apr 09 2011 16:24:26 GMT+0800 (CST)” }
- > db.blog.find({“author” : “huangz”})
- { “_id” : ObjectId(“4da0177a0fcb4b53ae3ba9fc”), “title” : “First day with MongoDB”, “author” : “huangz”, “content” : “Today, i try MongoDB, it\’s great!”, “date” : “Sat Apr 09 2011 16:21:51 GMT+0800 (CST)” }
- > db.blog.findOne()
- {
- “_id” : ObjectId(“4da0177a0fcb4b53ae3ba9fc”),
- “title” : “First day with MongoDB”,
- “author” : “huangz”,
- “content” : “Today, i try MongoDB, it\’s great!”,
- “date” : “Sat Apr 09 2011 16:21:51 GMT+0800 (CST)”
- }
- /* UPDATE 更新post记录,为它增加一个Key名字为comments的列表。*/
- > post
- {
- “title” : “First day with MongoDB”,
- “author” : “huangz”,
- “content” : “Today, i try MongoDB, it\’s great!”,
- “date” : “Sat Apr 09 2011 16:24:26 GMT+0800 (CST)”
- }
- > post.comments = []
- [ ]
- > db.blog.update({“title” : “First day with MongoDB”}, post)
- > post
- {
- “title” : “First day with MongoDB”,
- “author” : “huangz”,
- “content” : “Today, i try MongoDB, it\’s great!”,
- “date” : “Sat Apr 09 2011 16:24:26 GMT+0800 (CST)”,
- “comments” : [ ]
- }
- /* DELETE 删除post,下面两种方法等效 */
- db.blog.remove(post)
- db.blog.remove({“title” : “First day with MongoDB”})
深入查询:
1.条件查询
除了使用
- db.collection.find()
查询集合里所有文档外,我们可以指定特定的查询条件,比如以下语句只查询age为20的人。
- > db.user.find({“age”: 20})
- { “_id” : ObjectId(“4d7aff454dca002afb000000”), “age” : 20, “name” : “huangz” }
还可以使用多个查询条件,比如以下语句不但查询age为20的人,还查询name为huangz的人。
- > db.user.find({“age”: 20, “name” : “huangz”})
- { “_id” : ObjectId(“4d7aff454dca002afb000000”), “age” : 20, “name” : “huangz” }
还可以指定返回文档的数目
- > db.user.find().limit(1)
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
以及跳过指定数目的文档
- /* 所有文档 */
- > db.user.find()
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- /* 跳过第一个文档 */
- > db.user.find().skip(1)
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
排序指定文档的key,国际惯例,1为升序,-1为降序。
- /* 以name项的降序排列文档 */
- > db.user.find().sort({“name”: -1})
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
2.指定返回项
如果你只是对文档的某个(或数个)key感兴趣,那么你可以在查询时,指定要返回的key(注意:”_id”项默认总是被返回的,除非你明确指定不返回”_id”项)。
- /* 所有结果 */
- > db.user.find()
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- /* 只显示age为20岁的人所喜欢的水果,其他不显示。(比如一个水果贩子它只关心水果的受欢迎程度,不管你姓甚名谁。) */
- > db.user.find({“age”: 20}, {“favorite fruit”: 1})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “favorite fruit” : “apple” }
- /* 除了favorite fruit项之外,都显示 */
- > db.user.find({“age”: 20}, {“favorite fruit”: 0})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20 }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20 }
- /* 但是似乎没有办法同时指定显示和不显示。。。
- > db.user.find({“age”: 20}, {“favorite fruit”: 0, “name”: 1})
- error: {
- “$err” : “You cannot currently mix including and excluding fields. Contact us if this is an issue.”
- }
- */
3.条件查询(使用条件操作符)
可以使用MongoDB提供的查询条件操作符来进行查询,比如”$lt”, “$lte”, “$gt”, “$gte”分别代表 “<“, “<=”, “>”, “>=”。
- /* 只查询age <= 20的人 */
- > db.user.find({“age”: {“$lte” : 20}})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
“$ne”操作符代表”!=”,不等关系。
- /* 只查询age != 20的人 */
- > db.user.find({“age”: {“$ne” : 20}})
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
“$in”操作符表示,只要符合条件数组中的任何一个,就能被选中。
- /* 选中喜欢吃苹果apple或者香蕉banana的人 */
- > db.user.find({“favorite fruit”: {“$in” : [“apple”, “banana”]}})
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
“$nin”操作符表示,只要不符合条件数组的任何一个,就能被选中。
- /* 选中不喜欢吃苹果或香蕉的任何一种的人 */
- > db.user.find({“favorite fruit”: {“$nin” : [“apple”, “banana”]}})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
“$all”操作符表示,必须符合条件数组中的每一个,才能被选中。
- /* 所有文档 */
- > db.post.find()
- { “_id” : ObjectId(“4da079ee78d367d0af2ded04”), “title” : “today, a new database release”, “content” : “long long ago…”, “tags” : [ “nosql”, “mongodb”, “database” ] }
- { “_id” : ObjectId(“4da07aee78d367d0af2ded06”), “title” : “MySQL sucks”, “content” : “i use MySQL 10 year ago, is a long time…”, “tags” : [ “database”, “mysql” ] }
- /* 只选中tags项符合[“nosql”, “mongodb”, “database”]的文档(注意条件的顺序这里不重要) */
- > db.post.find({“tags”: {$all: [“nosql”, “mongodb”, “database”]}})
- { “_id” : ObjectId(“4da079ee78d367d0af2ded04”), “title” : “today, a new database release”, “content” : “long long ago…”, “tags” : [ “nosql”, “mongodb”, “database” ] }
“$not”操作符表示,对操作取反。
- /* 只选中age不小于或等于20的人 */
- > db.user.find({“age”: {“$not”: {“$lte”: 20}}})
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
“$or”操作符表示,至少符合条件数组的其中一个,就能被选中。
- /* 下面包含or的语句本该返回2个文档,但在我的测试中,1.8.1版本一个结果也不返回。。。真诡异 */
- > db.user.find()
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- /* WTF */
- > db.user.find({“$or”: [{“age”: 20}, {“age”: 21}]})
- >
- /* 本该是
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- */
“$and”操作符表示。。。嗯。。。MongoDB没有$and,你在写查询的时候提供多个条件就是了。
- > db.user.find({“name”:“huangz”, “age”: 20})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- /* 所有文档,分别有size为2和3的文档各一个 */
- > db.post.find()
- { “_id” : ObjectId(“4da079ee78d367d0af2ded04”), “title” : “today, a new database release”, “content” : “long long ago…”, “tags” : [ “nosql”, “mongodb”, “database” ] }
- { “_id” : ObjectId(“4da07aee78d367d0af2ded06”), “title” : “MySQL sucks”, “content” : “i use MySQL 10 year ago, is a long time…”, “tags” : [ “database”, “mysql” ] }
- /* 查找所有tags的size为3的文档 */
- > db.post.find({“tags”: {“$size”: 3}})
- { “_id” : ObjectId(“4da079ee78d367d0af2ded04”), “title” : “today, a new database release”, “content” : “long long ago…”, “tags” : [ “nosql”, “mongodb”, “database” ] }
- /* 返回所有post集合文档, 其中只返回大小为2的tags数组 */
- > db.post.find({}, {“tags”: {“$slice”: 2}})
- { “_id” : ObjectId(“4da079ee78d367d0af2ded04”), “tags” : [ “nosql”, “mongodb”, “database” ] }
- { “_id” : ObjectId(“4da07aee78d367d0af2ded06”), “tags” : [ “database”, “mysql” ] }
- /* $slice操作怎么又错了,悲剧阿。。。 */
- // Document 1
- { “foo” : [
- {
- “shape” : “square”,
- “color” : “purple”,
- “thick” : false
- },
- {
- “shape” : “circle”,
- “color” : “red”,
- “thick” : true
- }
- ] }
- // Document 2
- { “foo” : [
- {
- “shape” : “square”,
- “color” : “red”,
- “thick” : true
- },
- {
- “shape” : “circle”,
- “color” : “purple”,
- “thick” : false
- }
- ] }
- /* 失败查询 */
- db.foo.find({“foo.shape”: “square”, “foo.color”: “purple”})
- db.foo.find({foo: {“shape”: “square”, “color”: “purple”} } )
- /* 正确方法 */
- db.foo.find({foo: {“$elemMatch”: {shape: “square”, color: “purple”}}})
- /* 翻页程序 */
- var record_per_page = 50
- var pages = db.post.find().sort({“date”: -1}).limit(record_per_page)
- latest = loop_and_print_pages(pages)
- var pages = db.post.find({“date”: {“$gt”: latest.date}}).sort({“date”: 1}).limit(record_per_page)
- latest = loop_and_print_pages(pages)
- >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1)
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1)
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1)
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- >db.user.find().skip(Math.floor(Math.random()*db.user.find().count())).limit(1)
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- > db.user.find({“$query”: {}, “$snapshot”: true})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- /* 比如你经常要按升序查询名字,可以给名字(name)项加上索引 */
- > db.user.ensureIndex({“name”:1})
- > db.user.find().sort({“name”:1})
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- /* 使用了索引的查询 */
- > db.user.find({“name”: “huangz”, “age”: 20}).explain()
- {
- “cursor” : “BtreeCursor name_1_age_1”, // 索引的类型,索引成功一般为BasicCursor,name_1_age_1显示索引的名字
- “indexBounds” : [ // 索引查询的条件限制
- [
- {
- “name” : “huangz”,
- “age” : 20
- },
- {
- “name” : “huangz”,
- “age” : 20
- }
- ]
- ],
- “nscanned” : 1, // 扫描项目数量,项目可以是Object也可以是索引关键字
- “nscannedObjects” : 1, // 扫描过的对象数量
- “n” : 1, // 实际返回文档数量
- “millis” : 0 // 查询使用时间
- }
- /* 没有使用索引的查询 */
- > db.post.find({“title”: “feak title”}).explain()
- {
- “cursor” : “BasicCursor”,
- “indexBounds” : [ ],
- “nscanned” : 2,
- “nscannedObjects” : 2,
- “n” : 0,
- “millis” : 0
- }
- /* 删除name项的升序索引 */
- db.user.dropIndexes({“name”: 1})
- /* 比如你以下两个查询都可以被索引 */
- db.user.ensureIndex({“name”: 1, “age”: 1})
- db.user.find({“name”: “huangz”, “age”: 20})
- db.user.find({“age”: 20, “name”: “huangz”})
- /* 以下两个就不一样了,只有第一个查询被索引,第二个查询没有用到索引 */
- db.user.find().sort({“name”: 1, “age”: 1})
- db.user.find().sort({“age”: 1, “name”: 1})
- > db.user.find().count()
- 3
- > db.user.find({“name”:“huangz”}).count()
- 1
- /* 用distinct计算出,集合里只有20岁和30岁的人。 */
- > db.user.find()
- { “_id” : ObjectId(“4da070040d03918e09fe7dac”), “name” : “huangz”, “age” : 20, “favorite fruit” : “pear” }
- { “_id” : ObjectId(“4da070180d03918e09fe7dad”), “name” : “jack”, “age” : 20, “favorite fruit” : “apple” }
- { “_id” : ObjectId(“4da0702d0d03918e09fe7dae”), “name” : “peter”, “age” : 30, “favorite fruit” : “banana” }
- > db.user.distinct(“age”)
- [ 20, 30 ]
- /* group的一般参数如下:
- ns: 要处理的集合名称
- key: 需要group by处理的项
- reduce: reduce函数,对集合内的每个文档使用一次,接受两个函数,一个是目前迭代的文档,另一个是聚合计数器。
- initial: 聚合的初始值
- keyf: 可选,用函数对key中的值进行修改然后作为key参数传入到group。
- cond: 可选,相当于文档的filter,符合条件的文档才会被group处理。
- finalize: 可选,每个group处理之后的结果都传入到这个函数,可以用于修改最终group结果。
- 参数有点复杂,但整体还是很清晰的。
- 最好的例子还是参考mongodb主页的例子: http://www.mongodb.org/display/DOCS/Aggregation
- */
- db.test.group(
- { cond: {“invoked_at.d”: {$gte: “2009-11”, $lt: “2009-12”}}
- , key: {http_action: true}
- , initial: {count: 0, total_time:0}
- , reduce: function(doc, out){ out.count++; out.total_time+=doc.response_time }
- , finalize: function(out){ out.avg_time = out.total_time / out.count }
- } );
- /* MapReduce参数
- db.runCommand(
- { mapreduce : <collection>,
- map : <mapfunction>,
- reduce : <reducefunction>
- [, query : <query filter object>]
- [, sort : <sort the query. useful for optimization>]
- [, limit : <number of objects to return from collection>]
- [, out : <see output options below>]
- [, keeptemp: <true|false>] // 如果为true,则计算的结果作为数据保存到out参数指定的地方
- [, finalize : <finalizefunction>] // 处理所有计算结果的函数
- [, scope : <object where fields go into javascript global scope >] // 指定计算时候可以访问的外部变量
- [, verbose : true] // 提供计算时候的统计数据
- }
- );
- 关于out选项,当使用版本<=1.7.3时,可以指定变量或者集合名字作为参数。
- 如果使用版本>=1.7.4,参数名可以是以下:
- 1.collectionName: 结果覆盖同名集合
- 2.{replace: collectionName}: 同上
- 3.{merge: collectionName}: 将结果和集合里的数据合并,如果有同名的key,新的数据会覆盖旧的数据。
- 4.{reduce: collectionName}: 如果计算结果和集合里的数据有相同key的情况出现,将调用指定的reduce function。
- 5.{inline: 1}: 所有计算结果保存在内存当中。
- 详细参考MongoDB网站: http://www.mongodb.org/display/DOCS/MapReduce
- */
- > db.things.insert( { _id : 1, tags : [\’dog\’, \’cat\’] } );
- > db.things.insert( { _id : 2, tags : [\’cat\’] } );
- > db.things.insert( { _id : 3, tags : [\’mouse\’, \’cat\’, \’dog\’] } );
- > db.things.insert( { _id : 4, tags : [] } );
- > // map function
- > m = function(){
- … this.tags.forEach(
- … function(z){
- … emit( z , { count : 1 } );
- … }
- … );
- …};
- > // reduce function
- > r = function( key , values ){
- … var total = 0;
- … for ( var i=0; i<values.length; i++ )
- … total += values[i].count;
- … return { count : total };
- …};
- > res = db.things.mapReduce(m,r);
- > res
- {“timeMillis.emit” : 9 , “result” : “mr.things.1254430454.3” ,
- “numObjects” : 4 , “timeMillis” : 9 , “errmsg” : “” , “ok” : 0}
- > db[res.result].find()
- {“_id” : “cat” , “value” : {“count” : 3}}
- {“_id” : “dog” , “value” : {“count” : 2}}
- {“_id” : “mouse” , “value” : {“count” : 1}}
- > db[res.result].drop()