MongoDB查詢實現 笛卡爾積,Union All 和Union 功能
此篇文章及以後的文章大部分都是從聚合管道(aggregation pipeline)的一些語法為基礎講解的,如果不理解聚合管道的話,可以先學習一下會比較容易理解. 可以參考 mongoDB Documentation 的 Pipeline Aggregaion Stages.
何為Union All 和 Union
Union All指令的目的是將兩個結果放在一起並且不管是否有重複,Union指令則把結果合併且去掉重複結果.
SQL中的實現Union All
在sql中,我們可以很簡單的就實現 Union All 的效果.比如在sql中,我們的資料是
tableA | id | type | tableB | id | type |
---|---|---|---|---|---|
1 | OPEN | 1 | OPEN | ||
2 | CLOSE | 2 | ISSUE | ||
3 | REJECT | 3 | VOID | ||
4 | REQUEST |
我們在sql中 Union All 的寫法是:
select a.type as type from tableA a Union All select b.type from tableB b;
得到的結果是:
type |
---|
OPEN |
CLOSE |
REJECT |
REQUEST |
OPEN |
ISSUE |
VOID |
MongoDB 的語法實現
在MongoDB中,對於給我們表聯結相關使用的函式,有aggregate中的$lookup函式, 參照我們的官方例子,我們很容易就能理解$lookup函式的作用,相當於我們在sql裡面的表聯結,如:
select a.*,b.* from tableA a,tableB as b where a._id = b.tableAId;
在$lookup函式中,有以下引數為必填:
-
from: <collection to join>,
//等價於上面的 tableB -
localField: <field from the input documents>,
// 等價於上面的 a._id -
foreignField: <field from the documents of the "from" collection>,
//等價於上面的 b.tableAId -
as: <output array field>
// 等價於上面as後面的 b
那麼如何使用聯結作用的函式來實現Union All 的作用呢?其實很簡單,在上面的4個引數裡面,localField 和 foreignField 是必填的,但是在mongo裡面,我們可以填寫一個無效的field(不存在表裡面的field)來實現我們的效果
我們的測試資料如下:
tableA
{"_id":"1","type":"OPEN"}
{"_id":"2","type":"CLOSE"}
{"_id":"3","type":"REJECT"}
{"_id":"4","type":"REQUECT"}
tableB
{"_id":"1","type":"OPEN"}
{"_id":"2","type":"ISSUE"}
{"_id":"3","type":"VOID"}
1. 實現笛卡爾積
首先我們寫的查詢語句如下,將localField 和 foreignField隨便填寫一個String語句,只要是不在表裡面存在的field即可
db.tableA.aggregate([
{
$lookup:{
from:"tableB",
localField:"invalidField",
foreignField:"testField",
as:"tableB"
}
}
])
查詢結果:
_id | type | tableB |
---|---|---|
1 | OPEN | [3 elements] |
1 | CLOSE | [3 elements] |
1 | REJECT | [3 elements] |
1 | REQUEST | [3 elements] |
在MongoDB裡面,field的判斷是空等於空的,value的判斷空是不等於空的. 兩個等於空的field去比較,相當於 在sql 裡面 where 1=1 的寫法.
可以看到,我們tableA的每一條記錄都匹配到了tableB的3個元素(所有資料),此時只要我們將tableB的記錄 $unwind出來,就實現了笛卡爾積的效果了.
$unwind語法如下:
db.tableA.aggregate([
{
$lookup:{
from:"tableB",
localField:"invalidField",
foreignField:"testField",
as:"tableB"
}
},
{
$unwind:{
path:"$tableB"
}
},
{
$project:{
_id:1,
type:1,
tableBId:"$tableB._id",
tableBType:"$tableB.type"
}
}
])
等價於sql語句:
select a.id,a.type,b.id as tableId,b.type as tableBType from tableA a,tableB bwhere 1=1;
查詢結果可以自行測試
2. 實現Union
在MongoDB裡面,有一個$setUnion的函式,$setUnion函式被union的引數必需是陣列, 在我們tableA lookup tableB之後返回來的結果,tableB已經是一個一個數組了,但是我們的tableA的type是一個字串值,所以我們需要先將tableA的內容先轉為陣列,才能進行union.
All operands of $setUnion must be arrays.
將tableA裡面的所有記錄轉為一個數組需要用到$gourp函式裡面的$push功能. 查詢語法如下:
db.tableA.aggregate([
{
$group:{
_id:"any",
tableA:{
$push: "$$ROOT"
}
}
}
])
查詢結果:
_id | tableA |
---|---|
any | [4 elements] |
因為$gourp函式裡面的_id屬性是必選的,但是這裡我們不用到,所以填任意字串或者null都可以.使用$push之後,tableA的所有記錄,都被push到了我們命名為tableA的數組裡面, 此時我們在$lookup tableB看看結果如何. 查詢語法如下:
db.tableA.aggregate([
{
$group:{
_id:"any",
typeArray:{
$push: "$$ROOT"
}
}
},
{
$lookup:{
from:"tableB",
localField:"invalidField",
foreignField:"testField",
as:"tableB"
}
}
])
查詢結果:
_id | tableA | tableB |
---|---|---|
any | [4 elements] | [3 elements] |
可以看到,我們的tableA,和tableB的結果都變成了陣列,此時我們已經可以使用$setUnion函式去實現我們的Union效果了
db.tableA.aggregate([
{
$group:{
_id:"any",
tableA:{
$push: "$$ROOT"
}
}
},
{
$lookup:{
from:"tableB",
localField:"invalidField",
foreignField:"testField",
as:"tableB"
}
},
{
$project:{
_id:0,
allValue:{
$setUnion:["$tableA","$tableB"]
}
}
}
])
查詢結果:
allValue |
---|
[6 elements] |
此處只有6個元素在數組裡面,已經把重複的去掉了,可以說我們的Union效果已經實現,之後在把結果用$unwind展開即可. 查詢語法如下:
db.tableA.aggregate([
{
$group:{
_id:"any",
tableA:{
$push: "$$ROOT"
}
}
},
{
$lookup:{
from:"tableB",
localField:"invalidField",
foreignField:"testField",
as:"tableB"
}
},
{
$project:{
_id:0,
allValue:{
$setUnion:["$tableA","$tableB"]
}
}
},
{
$unwind:{
path:"$allValue"
}
},
{
$project:{
_id:0,
type:"$allValue.type"
}
},
])
3. 實現Union All
實現Union All 的原理與union 的類似,我們可以在把tableA push 成一個數組前,新增一個field,或者只push type,那麼在union的時候,因為table A 和 table B field 數量不一致,那麼永遠不會合併成一行,因為它們任意一行都是不一樣的. 查詢語法如下:
db.tableA.aggregate([
{
$group:{
_id:"any",
tableA:{
$push: {type:"$type"}
}
}
},
{
$lookup:{
from:"tableB",
localField:"invalidField",
foreignField:"testField",
as:"tableB"
}
},
{
$project:{
_id:0,
allValue:{
$setUnion:["$tableA","$tableB"]
}
}
},
{$unwind:"$allValue"},
{
$project:{
_id:0,
type:"$allValue.type"
}
}
])
Union All 的結果,可以通過group的方式去重來變成 Union 的效果. $group函式在此就不再細講,可以參考官網.
總結
在我們的MongoDB官方文件裡面介紹了一些函式的基本語法,但是功能方面比較Oracle等傳統關係型資料庫來說還是比較少的,因為一些如本文講的Union等這些功能,只能根據現有的功能去實現. 而在官網和網上現有的資料裡面,是沒有實現Union這些功能的介紹的,因此寫下了這篇文件.