原文来自: MongoDB中的多表关联查询、聚合管道
详解MongoDB中的多表关联查询(
unwind、
project)
你越是认真生活,你的生活就会越美好
——弗兰克·劳埃德·莱特
《人生果实》经典语录
管道的概念
管道
在Unix和Linux中一般用于
将当前命令的输出结果
作为
下一个命令的参数
。
MongoDB
的
聚合管道
将MongoDB文档
在一个管道处理完毕后
将结果传递给
下一个管道
处理。管道操作是可以重复的。
MongoDB
中聚合(
aggregate
)主要用于
处理数据
(诸如统计平均值,求和等),并返回计算后的数据结果。
聚合框架
是
MongoDB的高级查询语言
,它允许我们通过
转换和合并多个文档中的数据
来
生成新的单个文档中不存在的信息
。
聚合管道操作主要包含下面几个部分:
是将每个输入待处理的文档,经过
$lookup
阶段的处理,输出的新文档中会包含一个
新生成的数组列
(户名可根据需要命名新key的名字 )。
数组列
存放的数据是来自
被Join 集合的适配文档
,如果没有,集合为空(即 为
[ ]
)
(注:null = null 此为真)
假设 有 订单集合, 存储的测试数据 如下:
db.orders.insert(
"id": 1,
"item": "almonds",
"price": 12,
"quantity": 2
"id": 2,
"item": "pecans",
"price": 20,
"quantity": 1
"id": 3
其中item
对应 数据为 商品名称。
另外 一个 就是就是 商品库存集合 ,存储的测试数据 如下:
db.inventory.insert([
"id": 1,
"sku": "almonds",
description: "product 1",
"instock": 120
"id": 2,
"sku": "bread",
description: "product 2",
"instock": 80
"id": 3,
"sku": "cashews",
description: "product 3",
"instock": 60
"id": 4,
"sku": "pecans",
description: "product 4",
"instock": 70
"id": 5,
"sku": null,
description: "Incomplete"
"id": 6
此集合中的sku
数据等同于 订单 集合中的 商品名称。
在这种模式设计下,如果要查询订单表对应商品的库存情况
,应如何写代码呢?
很明显这需要两个集合Join
。
实现语句如下
db.getCollection('orders').aggregate([
$lookup:
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
返回的执行结果如下:
分析查询语句和结果,回扣$lookup
定义,可以将上面的处理过程,描述如下:
从集合order
中逐个获取文档处理,拿到一个文档后,会根据localField
值 遍历
被 Join的 inventory集合
(from: “inventory”),看inventory集合文档
中 foreignField
值是否与之相等
。
如果相等,就把符合条件的inventory文档
整体内嵌到聚合框架新生成的文档中
,并且新key 统一命名为 inventory_docs
。
考虑到符合条件的文档不唯一
,这个Key对应的Value是个数组形式
。
原集合中Key
对应的值为Null值
或不存在
时,需特别小心。
在以下的说明中,为描述方便,将
from对应的集合
定义为 被join集合
;
待聚合的表
成为源表
;
将localField
和foreignField
对应的Key 定义为比较列
。
这个示例中,一共输出了三个文档,在没有再次聚合($match
)的条件下,这个输出文档数量
是以输入文档的数量
来决定的(由order来决定),而不是以被Join的集合(inventory)文档数量决定。
在此需要特别强调
的是输出的第三个文档。在源库中原文档没有要比较的列(即item值不存在,既不是Null值,也不是值为空
),此时 和 被Join 集合
比较,如果 被Join集合中 比较列 也恰好 为NUll 或 不存在的值,此时,判断相等
,即会把 被Join集合中 比较列 为NUll 或 值不存在 文档 吸收进来。
假设 源表(order) 中比较列 为某一个值,而此值在待比较表(inventory)的所有文档中都不存在,那么查询结果会是什么样子呢?
order 集合
在现有数据的基础上,再被insert
进一笔测试数据,这个订单的商品为Start
,在库存商品中根本没有此数据。
db.orders.insert({
"id": 4,
"item": "Start",
"price": 2000,
"quantity": 1
order集合的文档数量由之前的3个增长为4个。
再次执行查询
db.getCollection('orders').aggregate([
$lookup:
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
此时查看结果
在这里插入图片描述
查询出的结果也由之前的3个变成了4个
。比较特别的是第四个文档 ,其新增列 为 "inventory_docs" : [ ] ,即值为空
。所以,此时,实现的功能非常像关系型数据库的 left join。
那么,可不可以只筛选出新增列为空的文档
呢?
即我们查询出,比较列的条件下,筛选出只在A集合中,而不在集合B的文档
呢? 就像关系数据库中量表Join的 left join on a.key =b.key where b.key is null .
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
再次聚合一下就可以了,来一次$match
就可以了。
执行的语句调整一下就OK了。
db.getCollection('orders').aggregate([
$lookup:
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
{ $match : {"inventory_docs" : [ ]} }
执行结果如下
image