绝好的MongoDB学习资料之三. Schema Design

绝好的MongoDB学习资料之三. Schema Design

 

1. Document-Oriented

MongoDB 是一种面向文档(document-oriented)的数据库,其内存储的是一种 JSON-like 结构化数据。尽管拥有和关系型数据库 Database/Table 类似的的 DB/Collection 概念,但同一 Collection 内的 Document 可以拥有不同的属性。

(注: 以下 > 提示符表示 mongo JS 代码,>>> 为 Python 代码)

 

 
  1. > use blog

  2. switched to db blog

  3.  
  4. > db.users.insert({name:"user1", age:15})

  5. > db.users.insert({name:"user2", age:20, sex:1})

  6.  
  7. > db.users.find()

  8. { "_id" : ObjectId("4c479885089df9b53474170a"), "name" : "user1", "age" : 15 }

  9. { "_id" : ObjectId("4c479896089df9b53474170b"), "name" : "user2", "age" : 20, "sex" : 1 }

 

可以通过 $exists 判断某个字段是否存在。

 

 
  1. > db.users.find({sex:{$exists:true}})

  2. { "_id" : ObjectId("4c479896089df9b53474170b"), "name" : "user2", "age" : 20, "sex" : 1 }

 

2. Embed vs. Reference

Document 采取 JSON-like 这种层级结构,因此我们可以直接用嵌入(Embed)代替传统关系型数据库的关联引用(Reference)。

 

 
  1. > u = db.users.findOne({name:"user1"})

  2. {

  3. "_id" : ObjectId("4c479885089df9b53474170a"),

  4. "name" : "user1",

  5. "age" : 15

  6. }

  7.  
  8. > u.address = ["address1", "address2"]

  9. [ "address1", "address2" ]

  10.  
  11. > db.users.save(u)

  12.  
  13. > db.users.findOne({name:"user1"})

  14. {

  15. "_id" : ObjectId("4c479885089df9b53474170a"),

  16. "name" : "user1",

  17. "age" : 15,

  18. "address" : [

  19. "address1",

  20. "address2"

  21. ]

  22. }

  23.  
  24. > db.users.findOne({address:"address1"})

  25. {

  26. "_id" : ObjectId("4c479885089df9b53474170a"),

  27. "name" : "user1",

  28. "age" : 15,

  29. "address" : [

  30. "address1",

  31. "address2"

  32. ]

  33. }

 

MongoDB 支持以 "." 分割的 namespace 路径,但需要注意 key 不能以 "$" 开头,不能包含 "." 字符 (条件表达式中的多级路径须用引号)。

 

 
  1. > u = db.users.findOne({address:"address1"})

  2. {

  3. "_id" : ObjectId("4c479885089df9b53474170a"),

  4. "name" : "user1",

  5. "age" : 15,

  6. "address" : [

  7. "address1",

  8. "address2"

  9. ]

  10. }

  11.  
  12. > u.im = {msn:"user1@hotmail.com", qq:12345678}

  13. { "msn" : "user1@hotmail.com", "qq" : 12345678 }

  14.  
  15. > db.users.save(u)

  16.  
  17. > u = db.users.findOne({"im.qq":12345678})

  18. {

  19. "_id" : ObjectId("4c479885089df9b53474170a"),

  20. "name" : "user1",

  21. "age" : 15,

  22. "address" : [

  23. "address1",

  24. "address2"

  25. ],

  26. "im" : {

  27. "msn" : "user1@hotmail.com",

  28. "qq" : 12345678

  29. }

  30. }

  31.  
  32. > u.im.qq

  33. 12345678

  34.  
  35. > u.im.msn

  36. user1@hotmail.com

  37.  
  38. > db.users.update({"im.qq":12345678}, {$set:{"im.qq":12345}})

  39.  
  40. > u = db.users.findOne({"im.qq":12345})

  41. {

  42. "_id" : ObjectId("4c479885089df9b53474170a"),

  43. "name" : "user1",

  44. "age" : 15,

  45. "address" : [

  46. "address1",

  47. "address2"

  48. ],

  49. "im" : {

  50. "msn" : "user1@hotmail.com",

  51. "qq" : 12345

  52. }

  53. }

  54.  
  55. > u = db.users.find({"im.qq":{$exists:true}}, {"im.qq":1})

  56. { "_id" : ObjectId("4c479885089df9b53474170a"), "im" : { "qq" : 12345 } }

 

注意: 由于每篇文档都包含完整的 key 数据,因此使用尽可能短的 key 可以有效节省存储空间。

3. Operator

(1) $all: 判断数组属性是否包含全部条件。

 

 
  1. > db.users.insert({name:"user3", data:[1,2,3,4,5,6,7]})

  2. > db.users.insert({name:"user4", data:[1,2,3]})

  3.  
  4. > db.users.find({data:{$all:[2,3,4]}})

  5. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "name" : "user3", "data" : [ 1, 2, 3, 4, 5, 6, 7 ] }

 

注意和 $in 的区别。$in 是检查目标属性值是条件表达式中的一员,而 $all 则要求属性值包含全部条件元素。

(2) $size: 匹配数组属性元素数量。

 

 
  1. > db.users.insert({name:"user3", data:[1,2,3,4,5,6,7]})

  2. > db.users.insert({name:"user4", data:[1,2,3]})

  3.  
  4. > db.users.find({data:{$size:3}})

  5. { "_id" : ObjectId("4c47a13bb48cde79c6780df1"), "name" : "user4", "data" : [ 1, 2, 3 ] }

 

(3) $type: 判断属性类型。

 

 
  1. > db.users.insert({name:"user5", t:1})

  2. > db.users.insert({name:"user6", t:1.34})

  3. > db.users.insert({name:"user7", t:"abc"})

  4.  
  5. > db.users.find({t:{$type:1}})

  6. { "_id" : ObjectId("4c47a231b48cde79c6780df2"), "name" : "user5", "t" : 1 }

  7. { "_id" : ObjectId("4c47a23eb48cde79c6780df3"), "name" : "user6", "t" : 1.34 }

  8.  
  9. > db.users.find({t:{$type:2}})

  10. { "_id" : ObjectId("4c47a258b48cde79c6780df4"), "name" : "user7", "t" : "abc" }

 


类型值:

  • double:1
  • string: 2
  • object: 3
  • array: 4
  • binary data: 5
  • object id: 7
  • boolean: 8
  • date: 9
  • null: 10
  • regular expression: 11
  • javascript code: 13
  • symbol: 14
  • javascript code with scope: 15
  • 32-bit integer: 16
  • timestamp: 17
  • 64-bit integer: 18
  • min key: 255
  • max key: 127

(4) 使用正则表达式进行查询。

 

 

 
  1. > db.users.find({name:/user[135]/i}, {name:1})

  2. { "_id" : ObjectId("4c479885089df9b53474170a"), "name" : "user1" }

  3. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "name" : "user3" }

  4. { "_id" : ObjectId("4c47a231b48cde79c6780df2"), "name" : "user5" }

  5.  
  6. >>> users = db.users.find({"name" : {"$regex" : r"(?i)user[135]"}}, ["name"])

  7. >>> for u in users: print u

  8. ...

  9. {u'_id': ObjectId('4c479885089df9b53474170a'), u'name': u'user1'}

  10. {u'_id': ObjectId('4c47a133b48cde79c6780df0'), u'name': u'user3'}

  11. {u'_id': ObjectId('4c47a231b48cde79c6780df2'), u'name': u'user5'}

 

正则表达式标记:

i: 忽略大小写。
m: 默认为单行处理,此标记表示多行。
x: 扩展。

(5) 数组属性元素值匹配。

 

 
  1. > db.users.find({data:"abc"})

  2. { "_id" : ObjectId("4c47a481b48cde79c6780df5"), "name" : "user8", "data" : [ { "a" : 1, "b" : 10 }, 3, "abc" ] }

  3.  
  4. > db.users.find({data:{$elemMatch:{a:1, b:{$gt:5}}}})

  5. { "_id" : ObjectId("4c47a481b48cde79c6780df5"), "name" : "user8", "data" : [ { "a" : 1, "b" : 10 }, 3, "abc" ] }

 

{data:"abc"} 仅简单匹配数组属性是否包含该元素。$elemMatch 则可以处理更复杂的元素查找条件。当然也可以写成如下方式。

 

 
  1. > db.users.find({"data.a":1, "data.b":{$gt:5}})

  2. { "_id" : ObjectId("4c47a481b48cde79c6780df5"), "name" : "user8", "data" : [ { "a" : 1, "b" : 10 }, 3, "abc" ] }

 

还可以直接使用序号进行操作。

 

 
  1. > db.users.find({"data.2":3})

  2. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "name" : "user3", "data" : [ 1, 2, 3, 4, 5, 6, 7 ] }

  3. { "_id" : ObjectId("4c47a13bb48cde79c6780df1"), "name" : "user4", "data" : [ 1, 2, 3 ] }

 

(6) $not: 取反,表示返回条件不成立的文档。

似乎只能跟正则和 $mod 一起使用????

 

 
  1. > u = db.users.find({name:{$not:/user3/}}, {name:1})

  2. { "_id" : ObjectId("4c479885089df9b53474170a"), "name" : "user1" }

  3. { "_id" : ObjectId("4c47a13bb48cde79c6780df1"), "name" : "user4" }

  4. { "_id" : ObjectId("4c47a231b48cde79c6780df2"), "name" : "user5" }

  5. { "_id" : ObjectId("4c47a23eb48cde79c6780df3"), "name" : "user6" }

  6. { "_id" : ObjectId("4c47a258b48cde79c6780df4"), "name" : "user7" }

  7. { "_id" : ObjectId("4c47a481b48cde79c6780df5"), "name" : "user8" }

  8. { "_id" : ObjectId("4c479896089df9b53474170b"), "name" : "user2" }

 

(7) $unset: 和 $set 相反,表示移除文档属性。

 

 
  1. > u = db.users.find({name:"user1"})

  2. { "_id" : ObjectId("4c479885089df9b53474170a"), "name" : "user1", "age" : 15, "address" : [ "address1", "address2" ], "im" : { "msn" : "user1@hotmail.com", "qq" : 12345 } }

  3.  
  4. > db.users.update({name:"user1"}, {$unset:{address:1, im:1}})

  5.  
  6. > u = db.users.find({name:"user1"})

  7. { "_id" : ObjectId("4c479885089df9b53474170a"), "age" : 15, "name" : "user1" }

 

(8) $push: 和 $ pushAll 都是向数组属性添加元素。

 

 
  1. > u = db.users.find({name:"user1"})

  2. { "_id" : ObjectId("4c479885089df9b53474170a"), "age" : 15, "name" : "user1" }

  3.  
  4. > db.users.update({name:"user1"}, {$push:{data:1}})

  5.  
  6. > u = db.users.find({name:"user1"})

  7. { "_id" : ObjectId("4c479885089df9b53474170a"), "age" : 15, "data" : [ 1 ], "name" : "user1" }

  8.  
  9. > db.users.update({name:"user1"}, {$pushAll:{data:[2,3,4,5]}})

  10.  
  11. > u = db.users.find({name:"user1"})

  12. { "_id" : ObjectId("4c479885089df9b53474170a"), "age" : 15, "data" : [ 1, 2, 3, 4, 5 ], "name" : "user1" }

 

(9) $addToSet: 和 $push 类似,不过仅在该元素不存在时才添加 (Set 表示不重复元素集合)。

 

 
  1. > db.users.update({name:"user2"}, {$addToSet:{data:1}})

  2. > db.users.update({name:"user2"}, {$addToSet:{data:1}})

  3.  
  4. > u = db.users.find({name:"user2"})

  5. { "_id" : ObjectId("4c479896089df9b53474170b"), "data" : [ 1 ], "name" : "user2" }

  6. > db.users.update({name:"user2"}, {$push:{data:1}})

  7.  
  8. > u = db.users.find({name:"user2"})

  9. { "_id" : ObjectId("4c479896089df9b53474170b"), "data" : [ 1, 1 ], "name" : "user2" }

 

要添加多个元素,使用 $each。

 

 
  1. > db.users.update({name:"user2"}, {$addToSet:{data:{$each:[1,2,3,4]}}})

  2.  
  3. > u = db.users.find({name:"user2"})

  4. { "_id" : ObjectId("4c479896089df9b53474170b"), "data" : [ 1, 2, 3, 4 ], "name" : "user2" }

 

(10) $pop: 移除数组属性的元素,$pull 按值移除,$pullAll 移除所有符合提交的元素。

 

 
  1. > u = db.users.find({name:"user3"})

  2. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "data" : [ 1, 2, 3, 4, 5, 6, 7, 2, 3 ], "name" : "user3" }

  3.  
  4. > db.users.update({name:"user3"}, {$pop:{data:1}}) // 移除最后一个元素

  5.  
  6. > u = db.users.find({name:"user3"})

  7. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "data" : [ 1, 2, 3, 4, 5, 6, 7, 2 ], "name" : "user3" }

  8.  
  9. > db.users.update({name:"user3"}, {$pop:{data:-1}}) // 移除第一个元素

  10.  
  11. > u = db.users.find({name:"user3"})

  12. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "data" : [ 2, 3, 4, 5, 6, 7, 2 ], "name" : "user3" }

  13.  
  14. > db.users.update({name:"user3"}, {$pull:{data:2}}) // 移除全部 2

  15.  
  16. > u = db.users.find({name:"user3"})

  17. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "data" : [ 3, 4, 5, 6, 7 ], "name" : "user3" }

  18.  
  19. > db.users.update({name:"user3"}, {$pullAll:{data:[4,5,6]}}) // 移除 4,5,6

  20.  
  21. > u = db.users.find({name:"user3"})

  22. { "_id" : ObjectId("4c47a133b48cde79c6780df0"), "data" : [ 3, 7 ], "name" : "user3" }

 

(11) $where: 用 JS 代码来代替有些丑陋的 $lt、$gt。

MongoDB 内置了 Javascript Engine (SpiderMonkey)。可直接使用 JS Expression,甚至使用 JS Function 写更复杂的 Code Block。

 

 
  1. > for (var i = 0; i < 10; i++) db.users.insert({name:"user"+i, age:i})

  2.  
  3. > db.users.find()

  4. { "_id" : ObjectId("4c47b3372a9b2be866da226e"), "name" : "user0", "age" : 0 }

  5. { "_id" : ObjectId("4c47b3372a9b2be866da226f"), "name" : "user1", "age" : 1 }

  6. { "_id" : ObjectId("4c47b3372a9b2be866da2270"), "name" : "user2", "age" : 2 }

  7. { "_id" : ObjectId("4c47b3372a9b2be866da2271"), "name" : "user3", "age" : 3 }

  8. { "_id" : ObjectId("4c47b3372a9b2be866da2272"), "name" : "user4", "age" : 4 }

  9. { "_id" : ObjectId("4c47b3372a9b2be866da2273"), "name" : "user5", "age" : 5 }

  10. { "_id" : ObjectId("4c47b3372a9b2be866da2274"), "name" : "user6", "age" : 6 }

  11. { "_id" : ObjectId("4c47b3372a9b2be866da2275"), "name" : "user7", "age" : 7 }

  12. { "_id" : ObjectId("4c47b3372a9b2be866da2276"), "name" : "user8", "age" : 8 }

  13. { "_id" : ObjectId("4c47b3372a9b2be866da2277"), "name" : "user9", "age" : 9 }

  14.  
  15. > db.users.find({$where:"this.age > 7 || this.age < 3"})

  16. { "_id" : ObjectId("4c47b3372a9b2be866da226e"), "name" : "user0", "age" : 0 }

  17. { "_id" : ObjectId("4c47b3372a9b2be866da226f"), "name" : "user1", "age" : 1 }

  18. { "_id" : ObjectId("4c47b3372a9b2be866da2270"), "name" : "user2", "age" : 2 }

  19. { "_id" : ObjectId("4c47b3372a9b2be866da2276"), "name" : "user8", "age" : 8 }

  20. { "_id" : ObjectId("4c47b3372a9b2be866da2277"), "name" : "user9", "age" : 9 }

  21.  
  22. > db.users.find("this.age > 7 || this.age < 3")

  23. { "_id" : ObjectId("4c47b3372a9b2be866da226e"), "name" : "user0", "age" : 0 }

  24. { "_id" : ObjectId("4c47b3372a9b2be866da226f"), "name" : "user1", "age" : 1 }

  25. { "_id" : ObjectId("4c47b3372a9b2be866da2270"), "name" : "user2", "age" : 2 }

  26. { "_id" : ObjectId("4c47b3372a9b2be866da2276"), "name" : "user8", "age" : 8 }

  27. { "_id" : ObjectId("4c47b3372a9b2be866da2277"), "name" : "user9", "age" : 9 }

  28.  
  29. > db.users.find({$where: function(){ return this.age > 7 || this.age < 3;}})

  30. { "_id" : ObjectId("4c47b3372a9b2be866da226e"), "name" : "user0", "age" : 0 }

  31. { "_id" : ObjectId("4c47b3372a9b2be866da226f"), "name" : "user1", "age" : 1 }

  32. { "_id" : ObjectId("4c47b3372a9b2be866da2270"), "name" : "user2", "age" : 2 }

  33. { "_id" : ObjectId("4c47b3372a9b2be866da2276"), "name" : "user8", "age" : 8 }

  34. { "_id" : ObjectId("4c47b3372a9b2be866da2277"), "name" : "user9", "age" : 9 }

  35.  
  36. >>> for u in db.users.find({"$where":"this.age > 7 || this.age < 3"}): print u

  37. ...

  38. {u'age': 0.0, u'_id': ObjectId('4c47b3372a9b2be866da226e'), u'name': u'user0'}

  39. {u'age': 1.0, u'_id': ObjectId('4c47b3372a9b2be866da226f'), u'name': u'user1'}

  40. {u'age': 2.0, u'_id': ObjectId('4c47b3372a9b2be866da2270'), u'name': u'user2'}

  41. {u'age': 8.0, u'_id': ObjectId('4c47b3372a9b2be866da2276'), u'name': u'user8'}

  42. {u'age': 9.0, u'_id': ObjectId('4c47b3372a9b2be866da2277'), u'name': u'user9'}

  43.  
  44. >>> for u in db.users.find().where("this.age > 7 || this.age < 3"): print u

  45. ...

  46. {u'age': 0.0, u'_id': ObjectId('4c47b3372a9b2be866da226e'), u'name': u'user0'}

  47. {u'age': 1.0, u'_id': ObjectId('4c47b3372a9b2be866da226f'), u'name': u'user1'}

  48. {u'age': 2.0, u'_id': ObjectId('4c47b3372a9b2be866da2270'), u'name': u'user2'}

  49. {u'age': 8.0, u'_id': ObjectId('4c47b3372a9b2be866da2276'), u'name': u'user8'}

  50. {u'age': 9.0, u'_id': ObjectId('4c47b3372a9b2be866da2277'), u'name': u'user9'}

  51.  
  52. >>> for u in db.users.find().where("function() { return this.age > 7 || this.age < 3;}"): print u

  53. ...

  54. {u'age': 0.0, u'_id': ObjectId('4c47b3372a9b2be866da226e'), u'name': u'user0'}

  55. {u'age': 1.0, u'_id': ObjectId('4c47b3372a9b2be866da226f'), u'name': u'user1'}

  56. {u'age': 2.0, u'_id': ObjectId('4c47b3372a9b2be866da2270'), u'name': u'user2'}

  57. {u'age': 8.0, u'_id': ObjectId('4c47b3372a9b2be866da2276'), u'name': u'user8'}

  58. {u'age': 9.0, u'_id': ObjectId('4c47b3372a9b2be866da2277'), u'name': u'user9'}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值