原文來自:MongoDB中的多表關聯(lián)查詢携悯、聚合管道
詳解MongoDB中的多表關聯(lián)查詢(
unwind、
project)
你越是認真生活龟劲,你的生活就會越美好
——弗蘭克·勞埃德·萊特
《人生果實》經(jīng)典語錄
管道的概念
管道
在Unix和Linux中一般用于將當前命令的輸出結果
作為下一個命令的參數(shù)
。
MongoDB
的聚合管道
將MongoDB文檔在一個管道處理完畢后
將結果傳遞給下一個管道
處理逊彭。管道操作是可以重復的咸灿。
聚合框架
MongoDB
中聚合(aggregate
)主要用于處理數(shù)據(jù)
(諸如統(tǒng)計平均值,求和等)侮叮,并返回計算后的數(shù)據(jù)結果避矢。
聚合框架
是MongoDB的高級查詢語言
,它允許我們通過轉換和合并多個文檔中的數(shù)據(jù)
來生成新的單個文檔中不存在的信息
囊榜。
聚合管道操作主要包含下面幾個部分:
命令 | 功能描述 |
---|---|
$project | 指定輸出文檔里的字段. |
$match | 選擇要處理的文檔审胸,與fine()類似。 |
$limit | 限制傳遞給下一步的文檔數(shù)量卸勺。 |
$skip | 跳過一定數(shù)量的文檔砂沛。 |
$unwind | 擴展數(shù)組,為每個數(shù)組入口生成一個輸出文檔曙求。 |
$group | 根據(jù)key來分組文檔碍庵。 |
$sort | 排序文檔映企。 |
$geoNear | 選擇某個地理位置附近的的文檔。 |
$out | 把管道的結果寫入某個集合。 |
$redact | 控制特定數(shù)據(jù)的訪問。 |
$lookup | 多表關聯(lián)(3.2版本新增) |
$lookup的功能及語法
主要功能
是將每個輸入待處理的文檔缴罗,經(jīng)過$lookup
階段的處理旁赊,輸出的新文檔中會包含一個新生成的數(shù)組列
(戶名可根據(jù)需要命名新key的名字 )。
數(shù)組列
存放的數(shù)據(jù)是來自被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 | 同一個數(shù)據(jù)庫下等待被Join的集合。 |
localField | 源集合中的match值囤攀,如果輸入的集合中,某文檔沒有 localField宫纬,這個Key(Field)焚挠,在處理的過程中,會默認為此文檔含哪怔,有 localField:null的鍵值對宣蔚。 |
oreignField | 待Join的集合的match值,如果待Join的集合中认境,文檔沒有foreignField值胚委,在處理的過程中,會默認為此文檔含有 foreignField:null的鍵值對叉信。 |
as | 為輸出文檔的新增值命名亩冬。如果輸入的集合中已存在該值,則會覆蓋掉 |
(注:null = null 此為真)
例子
假設 有 訂單集合硼身, 存儲的測試數(shù)據(jù) 如下:
db.orders.insert(
[
{
"id": 1,
"item": "almonds",
"price": 12,
"quantity": 2
},
{
"id": 2,
"item": "pecans",
"price": 20,
"quantity": 1
},
{
"id": 3
}
]
)
其中item
對應 數(shù)據(jù)為 商品名稱硅急。
另外 一個 就是就是 商品庫存集合 ,存儲的測試數(shù)據(jù) 如下:
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
數(shù)據(jù)等同于 訂單 集合中的 商品名稱佳遂。
在這種模式設計下营袜,如果要查詢訂單表對應商品的庫存情況
,應如何寫代碼呢丑罪?
很明顯這需要兩個集合Join
荚板。
實現(xiàn)語句如下
db.getCollection('orders').aggregate([
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
])
返回的執(zhí)行結果如下:
分析查詢語句和結果,回扣$lookup
定義吩屹,可以將上面的處理過程跪另,描述如下:
從集合order
中逐個獲取文檔處理,拿到一個文檔后煤搜,會根據(jù)localField
值 遍歷
被 Join的 inventory集合
(from: “inventory”)免绿,看inventory集合文檔
中 foreignField
值是否與之相等
。
如果相等擦盾,就把符合條件的inventory文檔
整體內嵌到聚合框架新生成的文檔中
嘲驾,并且新key 統(tǒng)一命名為 inventory_docs
淌哟。
考慮到符合條件的文檔不唯一
,這個Key對應的Value是個數(shù)組形式
距淫。
原集合中Key
對應的值為Null值
或不存在
時绞绒,需特別小心婶希。
說明
在以下的說明中榕暇,為描述方便,將
from對應的集合
定義為 被join集合
喻杈;
待聚合的表
成為源表
彤枢;
將localField
和foreignField
對應的Key 定義為比較列
。
這個示例中筒饰,一共輸出了三個文檔缴啡,在沒有再次聚合(
$match
)的條件下,這個輸出文檔數(shù)量
是以輸入文檔的數(shù)量
來決定的(由order來決定)瓷们,而不是以被Join的集合(inventory)文檔數(shù)量決定业栅。在此需要
特別強調
的是輸出的第三個文檔。在源庫中原文檔沒有要比較的列(即item值不存在谬晕,既不是Null值碘裕,也不是值為空
),此時 和被Join 集合
比較攒钳,如果被Join集合中 比較列 也恰好 為NUll 或 不存在的值帮孔,此時,判斷相等
不撑,即會把 被Join集合中 比較列 為NUll 或 值不存在 文檔 吸收進來文兢。假設 源表(order) 中比較列 為某一個值,而此值在待比較表(inventory)的所有文檔中都不存在焕檬,那么查詢結果會是什么樣子呢姆坚?
order 集合
在現(xiàn)有數(shù)據(jù)的基礎上,再被insert
進一筆測試數(shù)據(jù)实愚,這個訂單的商品為Start
兼呵,在庫存商品中根本沒有此數(shù)據(jù)。
db.orders.insert({
"id": 4,
"item": "Start",
"price": 2000,
"quantity": 1
})
order集合的文檔數(shù)量由之前的3個增長為4個爆侣。
再次執(zhí)行查詢
db.getCollection('orders').aggregate([
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
])
此時查看結果
查詢出的結果也由之前的3個變成了4個
萍程。比較特別的是第四個文檔 ,其新增列 為 "inventory_docs" : [ ] 兔仰,即值為空
茫负。所以,此時乎赴,實現(xiàn)的功能非常像關系型數(shù)據(jù)庫的 left join忍法。
那么潮尝,可不可以只篩選出新增列為空的文檔
呢?
即我們查詢出饿序,比較列的條件下勉失,篩選出只在A集合中,而不在集合B的文檔
呢原探? 就像關系數(shù)據(jù)庫中量表Join的 left join on a.key =b.key where b.key is null .
MongoDB的聚合管道將MongoDB文檔在一個管道處理完畢后將結果傳遞給下一個管道處理乱凿。管道操作是可以重復的。
實現(xiàn)如下
再次聚合一下就可以了咽弦,來一次$match
就可以了徒蟆。
執(zhí)行的語句調整一下就OK了。
db.getCollection('orders').aggregate([
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
},
{ $match : {"inventory_docs" : [ ]} }
])
執(zhí)行結果如下
可以看出執(zhí)行結果只有一個文檔型型。這個文檔表明的含義是:訂單中有這個商品段审,但是庫存中沒有這個商品。
($lookup
只是聚合框架的一個stage
闹蒜,在其前前后后寺枉,都可以嵌入到其他的聚合管道的命令,例如$match.$group
等绷落。下面的說明姥闪,也可以說明一二)
- 以上的比較列都是單一的
Key/Value
,如果復雜一點,如果比較的列是數(shù)組嘱函,我們又該如何關聯(lián)呢甘畅?
我們接下來再來測試一把。將之前集合order 往弓、inventory
插入的數(shù)據(jù)清空疏唾。
插入此場景下的新數(shù)據(jù),向集合order
中插入的數(shù)據(jù)函似,如下:
db.orders.insert(
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" :[ "27 inch", "Retina display", "1920x1080" ],
"type" : "Monitor"
}
)
specs
對應的value是數(shù)組格式
槐脏。
向集合inventory
新插入的數(shù)據(jù) 如下:
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 對應的數(shù)據(jù)變成了字符串類型(原集合為數(shù)組)。 原因是 $unwind
{
$unwind: "$specs"
}
上面的結果前提下撇寞,只查詢specs為"27 inch"的數(shù)據(jù)
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中兩表關聯(lián)牌废,每個表都有條件,那么在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 集合間關聯(lián)后更新,在MongoDB中應該如何書寫呢?----借助 forEach 功能
由于篇幅限制(偷懶)
集合中的數(shù)據(jù)格式不再說明懂从。
需求:集合QQ_OrderReturn 和 RelQQ_ReconciliationDetails 關聯(lián)刷選授段,刷選符合條件,在更新QQ_OrderReturn的數(shù)據(jù)番甩。
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:將文檔中的某一個數(shù)組類型字段拆分成多條侵贵,每條包含數(shù)組中的一個值。
例子
db.orders.insert(
{
"id" : 1,
"item" : "MON1003",
"price" : 350,
"quantity" : 2,
"specs" :[ "27 inch", "Retina display", "1920x1080" ],
"type" : "Monitor"
}
)
對specs數(shù)組進行拆分:
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數(shù)組
中的每個數(shù)據(jù)都被分解成一個文檔,并且除了specs
的值不同外,其他的值都是相同的.
評論區(qū)提問解答
問題一:
在表A中有一個字段缘薛,類型為數(shù)組窍育,里面存放的表B的ID字段。所以一條表A的數(shù)據(jù)對應復數(shù)條表B的數(shù)據(jù)⊙谝耍現(xiàn)在我每次只查詢單條表A的數(shù)據(jù)蔫骂,想通過$unwind和$lookup
關聯(lián)查詢,但返回的結果是一個數(shù)組牺汤,數(shù)組里面除了插入的表B的數(shù)據(jù)不同之外其他的數(shù)據(jù)都是重復的。
我希望達成的效果
是最終只返回一條表A的數(shù)據(jù)浩嫌,這條數(shù)據(jù)中的那個數(shù)組里是我查詢并插入的所有表B的數(shù)據(jù)檐迟,這樣結果就不會有太多的重復,但我不知道該如何實現(xiàn)這樣的效果?
問題二
第二個問題是码耐,使用$lookup
后追迟,會將子表的所有數(shù)據(jù)全都插入到主表中,但我只希望獲取子表中特定的某幾項數(shù)據(jù)骚腥,其余全都不查詢敦间,或是不顯示。但$project
好像只對主表有用束铭,不能刪選掉插入的子表中的字段廓块,想請教下應該如何才能在聚合管道里刪選掉子表里的特定字段?
解決思路
因為Mongo操作我也不熟悉契沫,就我目前對Mongo操作的了解得到的 解決思路如下带猴,熟悉聚合管道相關的操作后,只要能從表里拿到數(shù)據(jù)懈万,不管數(shù)據(jù)是否有多余的拴清,想怎么改造,交給js實現(xiàn)会通,比較靈活
下面是問題涉及到的聚合管道操作實例口予,可以看看
希望可以幫到你
// 表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: '!',
}
]
)
執(zhí)行下面命令
db.getCollection('a').aggregate([
{$unwind: "$a4"}
])
執(zhí)行下面查詢得到
db.getCollection('a').aggregate([
{$unwind: "$a4"}, // 這一行處理后的結果 給一行處理
{$match : {"a4" : "002"}}
])
執(zhí)行下面的查詢得到
db.getCollection('a').aggregate([
{$unwind: "$a4"}, // 這一行處理后的結果 給一行處理
{$match : {"a4" : "002"}}, // 這一行處理后的結果 給一行處理
{$project: {a2:1}}
])
執(zhí)行下面的查詢得到
db.getCollection('a').aggregate([
{$unwind: "$a4"},
{
$lookup:
{
from: "b",
localField: "a4",
foreignField: "id",
as: "new"
}
}
])