
3.4 数组、内嵌
支持灵活的数据结构,是MongoDB这种文档数据库的一大优势。在面向对象的编程方式中,对象的成员可以是多种形式的,包括数组、子对象等。但是当我们希望将对象中的数据持久化到传统的关系型数据库中时,却发现没有很好的匹配模式。常见的一些做法如:
● 使用平铺式的多列式结构,如用tag1、tag2、tag3…表示数组中的若干个元素。
● 使用序列化的单列进行收敛,比如将数组或子对象转换为JSON字符串后存储到某个列,在读取时再进行解析。
无论哪一种方式,都是存在一些弊端的。平铺式的结构会导致列的数量膨胀,关系型数据库需要提前设计好Schema,但数组往往是动态的,无法满足快速变化的需求;单列序列化的方式带来了应用上的复杂性,数据库无法理解该列的内部结构,所能提供的操作只有“整存整取”。
MongoDB的文档模型充分理解了数组、内嵌式文档的数据结构,除了可以方便地对数组内的元素、内嵌文档的字段进行操作,还可以对这些“内嵌式”的字段进行索引以满足快速的查询。它们在使用方式上和普通的字段并没有什么大的不同,这是文档型数据库的一种强大的表现力。
值得注意的是,一些关系型数据库如MySQL、PostgreSQL在后来也支持数组和内嵌对象的类型,充分说明了该能力的重要性及普适性。
3.4.1 内嵌文档
让我们再回到前面的例子,一个book文档中可以包含作者的信息,包括作者名称、性别、家乡所在地等,代码如下:

一个显著的优点是,当我们查询book文档的信息时,作者的信息也会一并返回。如果只希望返回作者的名称,则可以指定author.name,代码如下:

也可以将author.name作为查询条件,代码如下:

如果作者信息需要修改,则可以指定其中的某个字段,比如修改作者的家乡所在地,代码如下:

3.4.2 数组
除了作者信息,book文档中还包含了若干个标签,这些标签可以用来表示book文档所包含的一些特征,如豆瓣读书中的标签(tag),如图3-3所示。

图3-3 豆瓣读书中的标签
我们用文档结构来表示,代码如下:

1.查询元素
在查询文档时,数组中的标签会被一起返回,如果只想获得最后一个标签元素,则可以用如下命令查询:

这里的$silice是一个查询操作符,用于指定数组的切片方式,与JavaScript中的用法类似。
2.修改元素
如果希望在标签中的这个数组末尾添加一个元素,则可以使用$push操作符,代码如下:

$push操作符可以配合其他操作符,一起实现不同的数组修改操作,比如和$each操作符配合可以用于添加多个元素,代码如下:

如果加上$slice操作符,那么只会保留经过切片后的元素,代码如下:

上述代码除了添加多个标签,最终只会保留最后的3个元素,即经过$slice操作后的结果。
3.根据元素查询
标签的一个重要作用就是用于查询,可以根据标签中的元素进行book文档的查找,代码如下:

上述代码会将所有标签数组中包含“伤感”一词的book文档都查找出来。如果希望查询同时存在多个标签的文档,则可以使用$all操作符,代码如下:

3.4.3 嵌套型的数组
数组元素可以是基本类型,也可以是内嵌的文档结构,我们尝试将标签的概念扩充一下,一个标签由tagKey和tagValue所组成,文档结构如下:

这种结构非常灵活,一个很适合的场景就是商品的多属性表示,如图3-4所示。

图3-4 电商平台中的商品属性
一个商品可以同时包含多个维度的属性,比如尺码、颜色、风格等,使用文档可以表示为:

以上的设计是一种常见的多值属性的做法,当我们需要根据属性进行检索时,需要用到$elementMatch操作符,代码如下:

当然,如果进行组合式的条件检索,则可以使用多个$elemMatch操作符,代码如下:

上述代码可以筛选出color=蓝色,并且size=大码的商品信息。