## 1.12 文档模型

从本节开始，我们将介绍文档数据库管理系统的数据组织与访问。凡是将文档模型作为数据模型的数据管理系统，我们统称为文档数据库系统。

### 1.12.1  文档结构

文档模型就是将数据组织成一个个的文档。每个文档通常表示现实世界中的一个对象或实体。刚接触文档模型的读者可能会对“文档”这个词很费解，因为它跟我们通常说的文本或文章并不属同一概念。文档模型中的文档特指如下的数据组织结构：

```BSon
[例1.46] 两个文档的数据组织结构
{
  "sno": "2022001",
  "sname": "沐辰",
  "gender": "male",
  "birthdate": "Jan 20, 2003",
  "department": "计算机"
}

{
  "sno": "2022191",
  "sname": "若汐",
  "gender": "female",
  "birthdate": "Dec 4, 2004",
  "department": "数学"
}
```

上例中，我们使用文档模型对”沐辰“和”若汐“两个人进行描述，得到了两个文档。熟悉Javascript或者Web编程的朋友可能会很快反应过来：这不就是JSON（JavaScript Object Notation）文件吗？没错。JSON正是使用文档模型对数据进行组织的。

在文档模型中，一个实体或对象的描述被放置在一对花括号“{”、“}”之中。花括号"{}"和描述内容则构成了一个文档。其中，描述内容为多个属性和属性值的集合。比如，关于”沐辰“的文档由学号（sno）、姓名（sname）、性别（gender）、生日（birthdate）、系（department）五个属性组成。每个属性都有一个取值（value）。其中，sname属性上的取值为“沐辰”，birthdate属性上的取值为“Jan 20, 2003”。

属性值的数据类型通常为字符串、数字、布尔值、日期、其他文档、数组和文档数组。其中，属性的取值为字符串、数组、布尔值、日期的例子如下所示：

```BSon
[例1.47]
{"x":"沐辰"}	/*字符串*/
{"x": 2001}       /*整数*/
{"x": 3.14}       /*浮点数*/
{"x": true}       /*布尔值*/
{"x": Date("1988-01-09")}       /*日期*/
```

当属性的取值为其他文档时，就形成了**嵌套文档**结构，如下面的例子所示：

```BSon
[例1.48] 嵌套文档结构
{
  "sno": "2022001",
  "sname": "沐辰",
  "gender": "male",
  "birthdate": {
    "day":20,
    "month":"Jan",
    "year":2003
  },
  "department": "计算机"
}
```

与例1.46相比，birthdate的属性值不再是一个字符串，而是一个文档对象，称为子文档。实际上，子文档里面还可以包含子文档。这样的嵌套可以无限进行下去，如下面的例子所示：

```BSon
[例1.49] 嵌套文档结构
{
  "sname": "沐辰",
  "birthdate": {
    "day":20,
    "month":"Jan",
    "year":2003
  },
  "father": {
    "name": "沐阳",
    "birthdate": {
      "day":24,
      "month":"Jun",
      "year":1973
    }
  }
}
```

考虑嵌套之后，文档的结构就变成了树形结构。图1-12-1展示了例1.48和例1.49两个文档的树形结构。在这个结构中，树的根节点代表文档自身，叶子节点代表属性值，中间节点则代表子文档。

<center>
	<img src="fig/chD1.1-doctree.jpg" width="99%" alt="tree image" />
	<br>
	<div display: inline-block; padding : 2px>
		图 1-12-1文档的树形结构
	</div>
</center>




属性值还可以是数组的形式。例如，如果Jason Chang有三个email地址，我们就可以将它们放进一个数组里。

```Bson
[例1.50] 属性值为数组的文档结构
{
  "sno": "2022001",
  "sname": "沐辰",
  "gender": "male",
  "birthdate": "Jan 20, 2003",
  "department": "计算机",
  "email": ["cmu@gmail.com", "cmu@163.com", "cmu@qq.com"]
}
```

数组里的元素除了是数值类型、字符串类型，也可以是子文档，如下例所示。有了数组之后，文档的结构仍然是树形结构。

```bson
[例1.51] 属性值为文档数组的文档结构
{
  "sno": "2022001",
  "sname": "沐辰",
  "gender": "male",
  "birthdate": "Jan 20, 2003",
  "department": "计算机",
  "courses": [
    {
      "cno"："1",
      "cname": "高数",
      "credit": 4,
      "grade": 92
    },
    {
      "cno"："3",
      "cname": "数据库",
      "credit": 4,
      "grade": 85
    }
  ]
}
```

在文档数据库中，文档的内容可能出现重复，以至于两个文档的属性和属性取值完全相同。为了更好地区分文档，数据管理系统通常使用一个称为“\_id”的属性作为文档的标识，并确保所有文档的”\_id“都是不同的。这样，用户可以通过"\_id"来唯一地识别一个文档。如下例所示，"\_id"属性取值采用的是12 bytes组成的ObjectID数据类型。"_id"属性通常是隐藏的，在需要时可以显示出来。

```bson
[例1.52] 文档结构中"_id"属性是文档的唯一标识
{
  "_id": ObjectId("5037ee4a1084eb3ffeef7228"),
  "sno": "2022001",
  "sname": "沐辰",
  "gender": "male",
  "birthdate": "Jan 20, 2003",
  "department": "计算机"
}

{
  "_id": ObjectId("4b2b9f67a1f631733d917a7a"),
  "x": 3.14
}
```

### 1.12.2  文档的匹配

文档模型是一种数据组织结构，也是人和计算机表达信息的一种共同方式。有了这种共同的表达方式，人和计算机可以对数据达成一致的理解。除了一个统一的信息表达方式，我们还需要一种统一的计算方式，用于描述数据的存取功能。文档模型上最常用的计算方式称为文档匹配。接下来，我们将描述文档是如何进行匹配的。

回到最初的例子，假设数据库里有描述"沐辰"和“若汐”的两个文档：

```BSon
{
  "sno": "2022001",
  "sname": "沐辰",
  "gender": "male",
  "birthdate": {
    "day":20,
    "month":"Jan",
    "year":2003
  },
  "department": "计算机"
}

{
  "sno": "2022191",
  "sname": "若汐",
  "gender": "female",
   "birthdate": {
    "day":4,
    "month":"Dec",
    "year":2004
  },
  "department": "数学"
}
```

那么，下面这个文档能够和上面的哪个文档实现匹配呢？

```BSon
{
  "gender": "female",
  "department": "数学"
}
```

直观地评判，答案显然是关于“若汐”的文档。用于匹配的文档只设定了gender和department两个属性，它和“若汐”的文档在这两个属性上的取值完全一致。通常，如果文档A的所有属性都在文档B中出现，并且两个文档在这些属性上的取值一致，那么我们可以称文档B是文档A的匹配。按照这个逻辑，下面这个文档则无法在数据库中找到匹配。

```BSon
{
  "gender": "male",
  "department": "数学"
}
```

匹配的计算方式可以延申到嵌套的文档，比如将下面的文档作为匹配对象：

```BSon
{
  "birthdate": {
    "year":2003
  }
}
```

直观判断，它可以和数据库中描述“沐辰”的文档匹配。这个文档设定了属性birthdate的子属性year的取值（简称birthdate.year上的取值）。这个取值正好和“沐辰”的文档在相应属性上的取值一致。如果我们将文档看成树状结构，如果文档A是文档B的匹配，当且仅当B一定能通过对A剪枝而得到。如图1-12-2所示，红色部分的树和整棵树形成匹配，也就是说，红色部分的树可以通过去掉蓝色部分得到。

<center>
	<img src="fig/chD1.1-docmatch.jpg" width="99%" alt="document matches" />
	<br>
	<div display: inline-block; padding : 2px>
		图 1-12-2 文档的匹配
	</div>
</center>



有时候，我们可以不对匹配对象的属性设定精确取值，而只设定一个取值范围条件。只要其他文档在相同属性上的取值满足这个条件，则可以形成匹配。比如，下面这个文档匹配对象：

```BSon
{
  "birthdate": {
    "year": >2003
  }
}
```

该文档声明自己在birthdate.year上的取值大于2003。根据匹配条件进行检索，它和“若汐”的文档形成匹配，因为J“若汐”在birthdate.year上的取值为2003，满足该匹配条件。

文档匹配实际上提供了一种查询方式，让我们可以在多个文档中找到满足匹配条件的文档。

### 练习题

**1**. 一个文档数据库里面有两类对象，书（book）和人（person）。书和人之间存在一种写作的关系，即某个人是某本书的作者。请问这种写作关系的信息应该如何存放？

 <ol type="A">
 <li>在描述书的文档中增加一个“作者（author）”属性，取值为其作者的名字。</li>
 <li>在描述人的文档中增加一个“著作（writing）”属性，取值为其著作的标题。</li>
 <li>在描述书的文档中增加一个“作者（author）”属性，取值为其作者的ID。</li>
 <li>A和B均可。</li>
 </ol>
**2**. 关系数据库让用户自行定义每一张表的Primary Key（主键），用于唯一识别表中的每一行数据。例如，学生表Student(sno, sname, birthday, gender)的主键可定义为sno，宿舍表rooms(dorm_no, room_no，size, floor)的主键可定义为(dorm_no, room_no) （由宿舍号和房间号组成的复合主键）。在文档数据库中，用户无需定义主键，每一个文档都可以由系统自动产生的缺省ID进行识别。换句话说，文档数据库的ID属性起到了Primary Key的作用。请思考：关系数据库的Primary Key机制和文档数据库的ID机制有什么不同，各自的优缺点是什么？

[**上一页<<**](chapter1.11-R.md) | [**>>下一页**](chapter1.13-D.md)
