MongoDB Views

1,118 阅读5分钟

什么是视图

和传统的关系型数据库类似,在MongoDB中也有view的概念,也就是我们所讲的视图.

MongoDB的view是一个可查询的对象,它的内容其实是由定义在其他view和collection上的聚合管道提供. 也就是讲view原始数据基于其他的view或者collection,数据最源头依然是collection,原始数据由aggregation pipeline加工后产生最终的实际数据.

基于上面的原理,view的数据实际上是虚拟的,并不在磁盘上持久化而是临时的保持在内存中,数据库重启时view的数据会清除,但是由于原始数据是持久化的,所以数据的可靠性不用担心,再次查询时会触发聚合管道的实时计算并再次保存在内存中

当我们查询视图的时候,视图起初并没有我们想要的数据,所以会产生按需的聚合管道计算

由于view的原始数据依赖于其他的collection或者view,它自身并独立的保存数据和相关的数据结构,所以视图是只读的,你不能对它做写入操作

A MongoDB view is a queryable object whose contents are defined by an aggregation pipeline on other collections or views.

MongoDB does not persist the view contents to disk.

A view's content is computed on-demand when a client queries the view.

MongoDB can require clients to have permission to query the view. MongoDB does not support write operations against views.

用途

  • 数据安全和敏感数据保护

Create a view on a collection of employee data to exclude any private or personal information (PII). Applications can query the view for employee data that does not contain any PII.

  • 原始数据的加工和提取

Create a view on a collection of collected sensor data to add computed fields and metrics. Applications can use simple find operations to query the data.

  • 组合多个collection为一个view,简化复杂的多collection间的关联查询

Create a view that joins two collections containing inventory and order history respectively. Applications can query the joined data without managing or understanding the underlying complex pipeline.

工作原理

当客户端查询视图的时候,MongoDB会将查询语句附加到定义视图的聚合管道后面,这就形成了一个组合了的聚合管道,MongoDB接着执行这个组合后的聚合管道并在这个过程中进行管道线优化

When clients query a view, MongoDB appends the client query to the underlying pipeline and returns the results of that combined pipeline to the client. MongoDB may apply aggregation pipeline optimizations to the combined pipeline.

快速上手

我们先设计一个场景:一个人有工资和每月的花销,花销有具体的账单,我们需要查询这个人某月的净收入和花费详情;同时密码是敏感数据,不能被直接查询。

原始数据

用户信息

db.user.insertMany([    {"username":"Thinktik","password":"dqw3er23","salary":10000,"expense":4534.23},    {"username":"Andy","password":"htyrt","salary":25000,"expense":14534.23},    {"username":"Sandy","password":"grtyr","salary":5000,"expense":7978},    {"username":"Tom","password":"tyhrt","salary":13000,"expense":3534.89},    {"username":"Jack","password":"jtyr","salary":230000,"expense":7534.43}])

账单信息

db.bills.insertMany([    {"customer":"Thinktik","details":{"watch":4534.23}},    {"customer":"Andy","details":{"motorcycle":14534.23}},    {"customer":"Sandy","details":{"phone":7978}},    {"customer":"Tom","details":{"bicycle":3534.89}},    {"customer":"Jack","details":{"pc":7534.43}},])

使用视图隐藏敏感数据

创建view

db.createCollection(
  "v_user",
  {
    "viewOn" : "user",
    "pipeline" : [
        {$project:{"password":0}}
    ]
  }
)

查询

我们发现密码信息被屏蔽了

think_db> db.createCollection(
...   "v_user",
...   {
.....     "viewOn" : "user",
.....     "pipeline" : [
.....         {$project:{"password":0}}
.....     ]
.....   }
... )
{ ok: 1 }
think_db> db.v_user.find({})
[
  {
    _id: ObjectId("61ce05630065f8ac6bef457f"),
    username: 'Thinktik',
    salary: 10000,
    expense: 4534.23
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4580"),
    username: 'Andy',
    salary: 25000,
    expense: 14534.23
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4581"),
    username: 'Sandy',
    salary: 5000,
    expense: 7978
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4582"),
    username: 'Tom',
    salary: 13000,
    expense: 3534.89
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4583"),
    username: 'Jack',
    salary: 230000,
    expense: 7534.43
  }
]

使用视图加工原始数据

计算当月的净收入

think_db> db.v_user.drop()
true
think_db> db.createCollection(
...   "v_user",
...   {
.....     "viewOn" : "user",
.....     "pipeline" : [
.....         {$project:{"password":0}},
.....         {$addFields:{"profit":{ $subtract: [ "$salary", "$expense" ] }}}
.....     ]
.....   }
... )
{ ok: 1 }
think_db> 

查询

我们看到已经成功的加工了原始数据,添加了一个field来表示净收入

think_db>  db.v_user.find({})
[
  {
    _id: ObjectId("61ce05630065f8ac6bef457f"),
    username: 'Thinktik',
    salary: 10000,
    expense: 4534.23,
    profit: 5465.77
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4580"),
    username: 'Andy',
    salary: 25000,
    expense: 14534.23,
    profit: 10465.77
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4581"),
    username: 'Sandy',
    salary: 5000,
    expense: 7978,
    profit: -2978
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4582"),
    username: 'Tom',
    salary: 13000,
    expense: 3534.89,
    profit: 9465.11
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4583"),
    username: 'Jack',
    salary: 230000,
    expense: 7534.43,
    profit: 222465.57
  }
]

使用视图组合多个collection

组合user和bill

think_db> db.v_user.drop()
true
think_db> db.createCollection(
...   "v_user",
...   {
.....     "viewOn" : "user",
.....     "pipeline" : [
.....         {$project:{"password":0}},
.....         {$addFields:{"profit":{ $subtract: [ "$salary", "$expense" ] }}},
.....         {$lookup:
.......             {
.........                 from: "bills",
.........                 localField: "username",
.........                 foreignField: "customer",
.........                 as: "details"
.........             }
.......         },
.....     ]
.....   }
... )
{ ok: 1 }
think_db> 

查询

我们看到已经成功的粘合了user和bill,很简单的就做到了关联查询

think_db>  db.v_user.find({})
[
  {
    _id: ObjectId("61ce05630065f8ac6bef457f"),
    username: 'Thinktik',
    salary: 10000,
    expense: 4534.23,
    profit: 5465.77,
    details: [
      {
        _id: ObjectId("61ce0a550065f8ac6bef4584"),
        customer: 'Thinktik',
        details: { watch: 4534.23 }
      },
      {
        _id: ObjectId("61ce0b8d0065f8ac6bef4589"),
        customer: 'Thinktik',
        details: { watch: 4534.23 }
      }
    ]
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4580"),
    username: 'Andy',
    salary: 25000,
    expense: 14534.23,
    profit: 10465.77,
    details: [
      {
        _id: ObjectId("61ce0a550065f8ac6bef4585"),
        customer: 'Andy',
        details: { motorcycle: 14534.23 }
      },
      {
        _id: ObjectId("61ce0b8d0065f8ac6bef458a"),
        customer: 'Andy',
        details: { motorcycle: 14534.23 }
      }
    ]
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4581"),
    username: 'Sandy',
    salary: 5000,
    expense: 7978,
    profit: -2978,
    details: [
      {
        _id: ObjectId("61ce0a550065f8ac6bef4586"),
        customer: 'Sandy',
        details: { phone: 7978 }
      },
      {
        _id: ObjectId("61ce0b8d0065f8ac6bef458b"),
        customer: 'Sandy',
        details: { phone: 7978 }
      }
    ]
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4582"),
    username: 'Tom',
    salary: 13000,
    expense: 3534.89,
    profit: 9465.11,
    details: [
      {
        _id: ObjectId("61ce0a550065f8ac6bef4587"),
        customer: 'Tom',
        details: { bicycle: 3534.89 }
      },
      {
        _id: ObjectId("61ce0b8d0065f8ac6bef458c"),
        customer: 'Tom',
        details: { bicycle: 3534.89 }
      }
    ]
  },
  {
    _id: ObjectId("61ce05630065f8ac6bef4583"),
    username: 'Jack',
    salary: 230000,
    expense: 7534.43,
    profit: 222465.57,
    details: [
      {
        _id: ObjectId("61ce0a550065f8ac6bef4588"),
        customer: 'Jack',
        details: { pc: 7534.43 }
      },
      {
        _id: ObjectId("61ce0b8d0065f8ac6bef458d"),
        customer: 'Jack',
        details: { pc: 7534.43 }
      }
    ]
  }
]

总结:

  • 视图本身并不拥有原始数据,其自身数据基于其他的视图或者集合
  • 视图使用聚合管道对原始数据做处理并产生自己的临时数据,临时数据缓存在内存中,当需要的数据不存在时就触发聚合管道的按需计算