MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)
-
- 管道的概念
- 聚合架構
- $lookup的功能及文法
-
- 主要功能
- 基本文法
- 例子
- 說明
- $unwind的功能及文法
-
- 例子
- 評論區提問解答
- 推薦閱讀
你越是認真生活,你的生活就會越美好
——弗蘭克·勞埃德·萊特
《人生果實》經典語錄
mongodb中文文檔
mongoose架構文檔
管道的概念
管道
在Unix和Linux中一般用于
将目前指令的輸出結果
作為
下一個指令的參數
。
MongoDB
的
聚合管道
将MongoDB文檔
在一個管道處理完畢後
将結果傳遞給
下一個管道
處理。管道操作是可以重複的。
聚合架構
MongoDB
中聚合(
aggregate
)主要用于
處理資料
(諸如統計平均值,求和等),并傳回計算後的資料結果。
聚合架構
是
MongoDB的進階查詢語言
,它允許我們通過
轉換和合并多個文檔中的資料
來
生成新的單個文檔中不存在的資訊
。
聚合管道操作主要包含下面幾個部分:
指令 | 功能描述 |
$project | 指定輸出文檔裡的字段. |
$match | 選擇要處理的文檔,與fine()類似。 |
$limit | 限制傳遞給下一步的文檔數量。 |
$skip | 跳過一定數量的文檔。 |
$unwind | 擴充數組,為每個數組入口生成一個輸出文檔。 |
$group | 根據key來分組文檔。 |
$sort | 排序文檔。 |
$geoNear | 選擇某個地理位置附近的的文檔。 |
$out | 把管道的結果寫入某個集合。 |
$redact | 控制特定資料的通路。 |
$lookup | 多表關聯(3.2版本新增) |
$lookup的功能及文法
主要功能
是将每個輸入待處理的文檔,經過
$lookup
階段的處理,輸出的新文檔中會包含一個
新生成的數組列
(戶名可根據需要命名新key的名字 )。
數組列
存放的資料是來自
被Join 集合的适配文檔
,如果沒有,集合為空(即 為
[ ]
)
基本文法
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
參數說明
- from:同一個資料庫下等待被Join的集合。
- localField: 源集合中的match值,如果輸入的集合中,某文檔沒有 localField,這個Key(Field),在處理的過程中,會預設為此文檔含有 localField:null的鍵值對。
- foreignField:待Join的集合的match值,如果待Join的集合中,文檔沒有foreignField值,在處理的過程中,會預設為此文檔含有 foreignField:null的鍵值對。
- as:為輸出文檔的新增值命名。如果輸入的集合中已存在該值,則會覆寫掉
例子
假設 有 訂單集合, 存儲的測試資料 如下:
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" : [ ]} }
])
執行結果如下
可以看出執行結果隻有一個文檔。這個文檔表明的含義是:訂單中有這個商品,但是庫存中沒有這個商品。
(
$lookup
隻是聚合架構的一個
stage
,在其前前後後,都可以嵌入到其他的聚合管道的指令,例如
$match.$group
等。下面的說明,也可以說明一二)
- 以上的比較列都是單一的
,如果複雜一點,如果比較的列是數組,我們又該如何關聯呢?Key/Value
我們接下來再來測試一把。将之前
集合order 、inventory
插入的資料清空。
插入此場景下的新資料,向
集合order
中插入的資料,如下:
db.orders.insert(
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" :[ "27 inch", "Retina display", "1920x1080" ],
"type" : "Monitor"
}
)
specs
對應的value是
數組格式
。
向
集合inventory
新插入的資料 如下:
db.inventory.insert({ "id" : 1, "sku" : "MON1003", "type" : "Monitor", "instock" : 120,"size" : "27 inch", "resolution" : "1920x1080" })
db.inventory.insert({ "id" : 2, "sku" : "MON1012", "type" : "Monitor", "instock" : 85,"size" : "23 inch", "resolution" : "1280x800" })
db.inventory.insert({ "id" : 3, "sku" : "MON1031", "type" : "Monitor", "instock" : 60,"size" : "23 inch", "display_type" : "LED" })
查詢的語句如下:
db.getCollection('orders').aggregate([
{
$unwind: "$specs"
}
])
結果如下
db.getCollection('orders').aggregate([
{
$unwind: "$specs"
},
{
$lookup:
{
from: "inventory",
localField: "specs",
foreignField: "size",
as: "inventory_docs"
}
}
])
查詢顯示結果如下:
輸出文檔中的
specs 對應的資料變成了字元串類型(原集合為數組)。 原因是 $unwind
{
$unwind: "$specs"
}
上面的結果前提下,隻查詢specs為"27 inch"的資料
db.getCollection('orders').aggregate([
{
$unwind: "$specs"
},
{
$lookup:
{
from: "inventory",
localField: "specs",
foreignField: "size",
as: "inventory_docs"
}
},
{
$match:{"specs": "27 inch"}
}
])
結果如下
再上面的前提下,隻輸出item,inventory_docs字段
db.getCollection('orders').aggregate([
{
$unwind: "$specs"
},
{
$lookup:
{
from: "inventory",
localField: "specs",
foreignField: "size",
as: "inventory_docs"
}
},
{
$match:{"specs": "27 inch"}
},
{
$project: {item:1, inventory_docs:1}
}
])
結果如下
最後一道題,在SQL中兩表關聯,每個表都有條件,那麼在MongoDB中應該如何書寫呢?
db.Rel_QQDetails.aggregate([
{ $match: {
ReconciliationId:CSUUID("bb54bee7-187f-4d38-85d7-88926000ac7a")
}
},
{ $lookup:
{
from: "Fct_QQStatements",
localField: "OrderId",
foreignField: "OrderStatementsId",
as: "inventory_docs"
}
},
{ $match : {
"inventory_docs.StatementsPriceException" :false
}
}
])
附加題,mongodb 集合間關聯後更新,在MongoDB中應該如何書寫呢?----借助 forEach 功能
由于篇幅限制(偷懶)
集合中的資料格式不再說明。
需求:集合QQ_OrderReturn 和 RelQQ_ReconciliationDetails 關聯刷選,刷選符合條件,在更新QQ_OrderReturn的資料。
db.QQ_OrderReturn.aggregate([
{$match:{"Status" : 21}},
{$match:{"Disabled" : 0}},
{$match:{"JoinResponParty" : "合作方"}},
{$match:{ SupplierSellerName:"(合作營)ABC陽澄湖蟹"}},
{
$lookup:
{
from: "RelQQ_ReconciliationDetails",
localField: "OrderReturnId",
foreignField: "OrderId",
as: "inventory_docs"
}
},
{ $match : {"inventory_docs" : [ ]} }
]).forEach(function(item){
db.QQ_OrderReturn.update({"id":item.id},{$set:{"Status":NumberInt(0)}})
})
$unwind的功能及文法
$unwind:将文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。
例子
db.orders.insert(
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" :[ "27 inch", "Retina display", "1920x1080" ],
"type" : "Monitor"
}
)
對specs數組進行拆分:
db.orders.aggregate([
{$unwind:"$specs"}
])
拆分結果:
/* 1 */
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" : "27 inch",
"type" : "Monitor"
}
/* 2 */
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" :"Retina display",
"type" : "Monitor"
}
/* 3 */
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" :"1920x1080",
"type" : "Monitor"
}
使用
$unwind
可以将
specs數組
中的每個資料都被分解成一個文檔,并且除了
specs
的值不同外,其他的值都是相同的.
評論區提問解答
問題一:
在表A中有一個字段,類型為數組,裡面存放的表B的ID字段。是以一條表A的資料對應複數條表B的資料。現在我每次隻查詢單條表A的資料,想通過
$unwind和$lookup
關聯查詢,但傳回的結果是一個數組,數組裡面除了插入的表B的資料不同之外其他的資料都是重複的。
我
希望達成的效果
是最終隻傳回一條表A的資料,這條資料中的那個數組裡是我查詢并插入的所有表B的資料,這樣結果就不會有太多的重複,但我不知道該如何實作這樣的效果?
問題二
第二個問題是,使用
$lookup
後,會将子表的所有資料全都插入到主表中,但我隻希望擷取子表中特定的某幾項資料,其餘全都不查詢,或是不顯示。但
$project
好像隻對主表有用,不能删選掉插入的子表中的字段,想請教下應該如何才能在聚合管道裡删選掉子表裡的特定字段?
解決思路
因為Mongo操作我也不熟悉,就我目前對Mongo操作的了解得到的 解決思路如下,熟悉聚合管道相關的操作後,隻要能從表裡拿到資料,不管資料是否有多餘的,想怎麼改造,交給js實作,比較靈活
下面是問題涉及到的聚合管道操作執行個體,可以看看
希望可以幫到你
// 表a
db.a.insert(
{
a1: 'hello',
a2: 'world',
a3: '!',
a4: ['001','002', '003']
}
)
// 表b
db.b.insert(
[
{
id: '001',
b1: 'do',
b2: 'better',
b3: '!',
},
{
id: '002',
b1: 'do',
b2: 'better',
b3: '!',
},
{
id: '003',
b1: 'do',
b2: 'better',
b3: '!',
}
]
)
執行下面指令
db.getCollection('a').aggregate([
{$unwind: "$a4"}
])
執行下面查詢得到
db.getCollection('a').aggregate([
{$unwind: "$a4"}, // 這一行處理後的結果 給一行處理
{$match : {"a4" : "002"}}
])
執行下面的查詢得到
db.getCollection('a').aggregate([
{$unwind: "$a4"}, // 這一行處理後的結果 給一行處理
{$match : {"a4" : "002"}}, // 這一行處理後的結果 給一行處理
{$project: {a2:1}}
])
執行下面的查詢得到
db.getCollection('a').aggregate([
{$unwind: "$a4"},
{
$lookup:
{
from: "b",
localField: "a4",
foreignField: "id",
as: "new"
}
}
])
謝謝你閱讀到了最後~
期待你關注、收藏、評論、點贊~
讓我們一起 變得更強
推薦閱讀
mongodb、mongoose相關
MongoDB 常用指令彙總 該有的都有