第二章 数据模型与查询语言
最后更新于:2022-04-02 04:18:25
[TOC]
## 数据模型
现在最著名的数据模型可能是SQL。它基于Edgar Codd在1970年提出的关系模型:数据被组织成**关系**(SQL中称作**表**),其中每个关系是**元组**(SQL中称作**行**)的无序集合。
### NoSQL的诞生
采用NoSQL数据库的背后有几个驱动因素,其中包括:
* 需要比关系数据库更好的可扩展性,包括非常大的数据集或非常高的写入吞吐量
* 相比商业数据库产品,免费和开源软件更受偏爱。
* 关系模型不能很好地支持一些特殊的查询操作
* 受挫于关系模型的限制性,渴望一种更具多动态性与表现力的数据模型
### 对象关系不匹配
目前大多数应用程序开发都使用面向对象的编程语言来开发,这导致了对SQL数据模型的普遍批评:如果数据存储在关系表中,那么需要一个笨拙的转换层,处于应用程序代码中的对象和表,行,列的数据库模型之间。模型之间的不连贯有时被称为**阻抗不匹配(impedance mismatch)**
### 多对一和多对多的关系
存储ID还是文本字符串,这是个**副本(duplication)**问题。当使用ID时,对人类有意义的信息(比如单词:Philanthropy)只存储在一处,所有引用它的地方使用ID(ID只在数据库中有意义)。当直接存储文本时,对人类有意义的信息会复制在每处使用记录中
使用ID的好处是,ID对人类没有任何意义,因而永远不需要改变:ID可以保持不变,即使它标识的信息发生变化。任何对人类有意义的东西都可能需要在将来某个时候改变
### 文档模型中的架构灵活性
- 文档数据库有时称为**无模式(schemaless)**,又称为读模式(schema-on-read)(数据的结构是隐含的,只有在数据被读取时才被解释)
- 传统的关系数据库是**写时模式(schema-on-write)**
读时模式类似于编程语言中的动态(运行时)类型检查,而写时模式类似于静态(编译时)类型检查
例子:
把姓名分为first name 和 last name
在文档数据库种只需要改为如下,动态的做一个判断
```
if (user && user.name && !user.first_name) {
// Documents written before Dec 8, 2013 don't have first_name
user.first_name = user.name.split(" ")[0];
}
```
在“静态类型”数据库模式中,通常会执行以下 迁移(migration) 操作
```
ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL
UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL
```
### 使用场景
当由于某种原因(例如,数据是异构的)集合中的项目并不都具有相同的结构时,读时模式更具优势。例如,如果:
* 存在许多不同类型的对象,将每种类型的对象放在自己的表中是不现实的。
* 数据的结构由外部系统决定。你无法控制外部系统且它随时可能变化
## 数据查询语言
关系模型包含了一种查询数据的新方法:SQL是一种**声明式**查询语言,而IMS和CODASYL使用**命令式**代码来查询数据库
命令式
```
function getSharks() {
var sharks = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].family === "Sharks") {
sharks.push(animals[i]);
}
}
return sharks;
}
```
声明式
```
SELECT * FROM animals WHERE family ='Sharks';
```
声明式查询语言它通常比命令式API更加简洁和容易。但更重要的是,它还隐藏了数据库引擎的实现细节,这使得数据库系统可以在无需对查询做任何更改的情况下进行性能提升
### MapReduce查询
- MapReduce是一个由Google推广的编程模型,用于在多台机器上批量处理大规模的数据【33】。一些NoSQL数据存储(包括MongoDB和CouchDB)支持有限形式的MapReduce,作为在多个文档中执行只读查询的机制
- MapReduce既不是一个声明式的查询语言,也不是一个完全命令式的查询API,而是处于两者之间
### 图数据模型
- 多对多关系是不同数据模型之间具有区别性的重要特征。如果你的应用程序大多数的关系是一对多关系(树状结构化数据),或者大多数记录之间不存在关系,那么使用文档模型是合适的
一个图由两种对象组成:**顶点(vertices)**(也称为**节点(nodes)**或**实体(entities)**),和**边(edges)**( 也称为**关系(relationships)**或**弧 (arcs)**)。多种数据可以被建模为一个图形
有几种不同但相关的方法用来构建和查询图表中的数据
1. 属性图模型
2. 三元组存储(triple-store)模型
#### 属性图模型
在属性图模型中,每个**顶点(vertex)**包括:
* 唯一的标识符
* 一组**出边(outgoing edges)**
* 一组**入边(ingoing edges)**
* 一组属性(键值对)
每条**边(edge)**包括:
* 唯一标识符
* **边的起点/尾部顶点(tail vertex)**
* **边的终点/头部顶点(head vertex)**
* 描述两个顶点之间关系类型的标签
* 一组属性(键值对)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/7b/c5/7bc56a89a9f588f3a9c2f40449098da8_1480x896.png)
1. 任何顶点都可以有一条边连接到任何其他顶点。没有模式限制哪种事物可不可以关联。
2. 给定任何顶点,可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动。
3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个清晰的数据模型。
图表在可演化性是富有优势的:当向应用程序添加功能时,可以轻松扩展图以适应应用程序数据结构的变化
##### Cypher查询语言
Cypher是属性图的声明式查询语言,为Neo4j图形数据库而发明
```
CREATE
(NAmerica:Location {name:'North America', type:'continent'}),
(USA:Location {name:'United States', type:'country' }),
(Idaho:Location {name:'Idaho', type:'state' }),
(Lucy:Person {name:'Lucy' }),
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
(Lucy) -[:BORN_IN]-> (Idaho)
```
美国移民到欧洲的人的Cypher查询
```
MATCH
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
RETURN person.name
```
#### 三元组存储triple-store)模型
三元组存储模式大体上与属性图模型相同,用不同的词来描述相同的想法
在三元组存储中,所有信息都以非常简单的三部分表示形式存储(**主语**,**谓语**,**宾语**),例如,三元组 (吉姆, 喜欢 ,香蕉) 中,吉姆 是主语,喜欢 是谓语(动词),香蕉 是对象
';