1. 程式人生 > >MongoDB查詢實現 笛卡爾積,Union All 和Union 功能

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這些功能的介紹的,因此寫下了這篇文件.