Skip to content

1.12 文档模型

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

1.12.01 文档结构

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

[例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”。

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

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

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

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

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

[例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两个文档的树形结构。在这个结构中,树的根节点代表文档自身,叶子节点代表属性值,中间节点则代表子文档。

tree image
图 1-12-1文档的树形结构

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

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

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

[例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"属性通常是隐藏的,在需要时可以显示出来。

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

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

1.12.02 文档的匹配

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

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

{
  "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": "数学"
}

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

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

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

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

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

{
  "birthdate": {
    "year":2003
  }
}

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

document matches
图 1-12-2 文档的匹配

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

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

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

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

练习题

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

  1. 在描述书的文档中增加一个“作者(author)”属性,取值为其作者的名字。
  2. 在描述人的文档中增加一个“著作(writing)”属性,取值为其著作的标题。
  3. 在描述书的文档中增加一个“作者(author)”属性,取值为其作者的ID。
  4. A和B均可。

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机制有什么不同,各自的优缺点是什么?

上一页<< | >>下一页