在像Couchbase这样的JSON数据库中建模数据时,开发人员和架构师有两种表示分层数据的选择。 第一个选项是在父对象中嵌入相关对象,如下所示:
另一种方法是分别表示相关对象,并通过标识来引用它们,如下所示:
具体何时使用一种形式或另一种形式超出了本文的范围。 但是无论选择哪一种,N1QL语言都允许开发人员在运行中转换到另一种语言。 这些转换是用两个操作符完成的,NEST和UNNEST。
假设我们有一个订单跟踪应用程序,我们设计了我们的数据库对象模型,让行项目存储在订单文档本身(嵌套)中,如下所示:
现在我们要计算每个订单的应付税款。 我们该怎么做? 订单层没有税务信息;我们只知道每一行项目的税。
我们必须做两件事。 首先,我们必须从订单中提取行项目,然后我们必须按订单汇总税收。 提取将通过一个UNNEST操作完成,聚合将通过一个聚合函数和一个分组依据完成。
我们可以用这个查询提取行项目:
这就产生了这个结果:
这是一个很大的结果。 让我们仔细看看。 结果有五个对象,每个对象有两个字段:“demo”包含整个原始(父)对象,“lineitems”包含原始对象中“lineitems”数组的一个元素。 所以我们提取了每个行项目,并保留了每个原始(父)对象,以备不时之需。
然后我们可以通过演示进行分组。order_id和sum line items。税,使用此查询:
这会产生这种微调结果:
让我们考虑一下基本查询是如何执行的。
计划是这样的:
该计划将扫描演示桶的主索引,获取它引用的syncnavigator 每条记录,并将其输入到扫描操作器中。
如果我们想对订单更有选择性,用指数就足够容易了。 我们向查询添加一个谓词,
我们还在新字段上添加了一个索引,
现在这个计划包含了一个闪亮的新索引扫描操作符,它使用新的索引。
但是,如果我们想对行项目有选择性呢? 假设我们只需要item_id=567的行项目,如下所示:
为此,我们需要一个数组索引,如下所示
这是每个订单中lineitems数组的item_id字段的索引。 然后我们试着解释,但什么也没发生。 没有选择索引。
问题是最初的查询。 Couchbase的当前版本非常注重如何使用数组索引,并且UNNEST子句必须采用特定的格式来匹配索引。 特别是,看看“l。索引定义中的item_id”。 它必须与查询中的字段名和前缀完全匹配。 如果我们像这样更改查询,事情就会正常工作:
然后解释向我们展示了一个不同的操作符,指示索引正在使用:
此处提供了有关阵列索引的更多信息:
现在,假设我们向演示桶中添加第三个文档,如下所示:
如果我们重新运行查询。
。我们看到了一个结果,但是新的订单不包括在内。 这是因为新订单没有行项目,因此不参与连接。
我们可以通过切换到左外部无纸通道来在结果中包含新的顺序,这包括文档,即使它们没有子对象。
查询。
。产生与前一个相同的结果,但这次包含了新的订单:
现在让我们假设我们已经将订单分解为主对象和从属对象,可能是因为主对象变得太大,并且产生了太多的网络流量,如下所示:
这如何迫使我们修改我们的查询? 我们可以这样连接对象和从属对象:
这就产生了这个结果:
然后,我们可以在子数据上进行隧道。要拆分各个行项目,请执行以下操作:
这会产生五个结果(每个行项目一个结果),结构如下:
从那里,我们可以像以前一样聚集。
现在让我们把问题翻过来。 如果数据以订单和行项目作为数据库中的单独条目开始(不需要),并且我们希望将它们与文档下的行项目组合在一起,会怎么样。 例如,我们可能想要每个订单,包括的项目和每个项目的数量。
在这种情况下,数据可以这样表示,使用七个独立的对象:
从这些文档中,我们可以使用NEST操作符,使用以下查询将单个对象转换为嵌套结构:
该查询产生以下结果:
同样,这是一个相当大的结果。 但是让我们仔细看看。 结果中有两个对象,每个订单一个。 每个对象有两个字段。 订单字段位于“ordr”下,行项目位于“li”下的数组中。
但是我们可以进一步简化。 我们只需要每个订单的“订单标识”,我们只需要每个行项目的“项目标识”和“数量”。 我们可以从ordr获得“order_id”。order_id,我们可以使用数组理解从“li”数组中提取“item_id”和“quantity ”,如下所示:
该查询生成以下修剪结果:
让我们回到最初的NEST查询,并检查Couchbase是否会执行它。
这让我们有了这个计划:
这里没有什么大的秘密。 Couchbase将扫描演示桶上的主索引,并探测主索引以获取每个行项目。
如果主对象上有一个条件可以由索引提供服务,Couchbase将使用它。 通过添加一个谓词和一个索引,您可以看到不同之处,如下所示:
现在该计划包含一个索引扫描操作符,它显示Couchbase将使用新的索引:
行项目的条件怎么样? 假设我们有一个谓词项id=555?
在这里,事实证明索引没有帮助。 NEST运算符右侧的谓词在NEST操作后应用。 这可能会对数据设计产生影响。 使用NEST操作时,任何需要选择性的字段都应该放在主体对象中,而不是下推到辅助对象中。
在前面的部分中,我们使用嵌套将键引用的外部对象分组到它们所属的顶级对象中。 但是没有子对象的对象呢?
我们可以通过向数据集添加第三个对象来测试这一点,如下所示:
然后我们重新运行查询,得到与之前完全相同的结果。 订单“6”不存在。 为什么? 问题是订单“6”没有lineitems数组,所以它不包含在连接中。
我们怎么能把它加到结果里呢? 首先,我们需要在查询中切换到左外嵌套操作符。 但这还不够。 仅这一更改就将包括演示桶中的行项目文档。 我们只需要订单文件,即使他们没有行项目。 这给了我们最后一个问题。
。产生这个结果,包括订单“6”:
有时对象变得太大而无法保存在单个文档中,将原始的单个对象分成子对象是有意义的。 例如,这里我们已经从原始对象中分离出原始行项目和价格字段。 请注意“1A”和“5A”文件。
那么我们怎样才能改造原来的对象呢?
我们首先加入“子”字段来重新创建原始对象。
。产生以下结果:
然后,我们也可以添加一个NEST子句来连接行项目。
这就产生了这个结果:
使用文档数据库时,我们可能需要从顶层文档中提取嵌套对象。 在Couchbase N1QL中,这是由UNNEST操作员完成的。
相反,我们可能需要将单个对象分组到它们的顶层文档下。 在Couchbase N1QL中,这是通过NEST操作符完成的。
您可以通过转到您的Couchbase管理控制台的“数据存储桶”选项卡并创建一个“演示”存储桶来创建在UNNEST部分中使用的数据集。 然后转到查询选项卡,并执行以下两个语句:
然后,您可以运行查询:
然后清空演示桶:
为NEST部分设置数据:
并运行查询: