【轉】MongoDB中的多表關聯(lián)查詢、聚合管道($lookup烘豹、$unwind瓜贾、$match、$project)

原文來自:MongoDB中的多表關聯(lián)查詢携悯、聚合管道

詳解MongoDB中的多表關聯(lián)查詢(lookup祭芦、unwind、match憔鬼、project)

你越是認真生活龟劲,你的生活就會越美好——弗蘭克·勞埃德·萊特
《人生果實》經(jīng)典語錄

mongodb中文文檔
mongoose框架文檔

管道的概念

管道在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集合喻杈;
待聚合的表成為源表彤枢;
localFieldforeignField對應的Key 定義為比較列

  1. 這個示例中筒饰,一共輸出了三個文檔缴啡,在沒有再次聚合($match)的條件下,這個輸出文檔數(shù)量以輸入文檔的數(shù)量來決定的(由order來決定)瓷们,而不是以被Join的集合(inventory)文檔數(shù)量決定业栅。

  2. 在此需要特別強調的是輸出的第三個文檔。在源庫中原文檔沒有要比較的列(即item值不存在谬晕,既不是Null值碘裕,也不是值為空),此時 和 被Join 集合比較攒钳,如果 被Join集合中 比較列 也恰好 為NUll 或 不存在的值帮孔,此時,判斷相等 不撑,即會把 被Join集合中 比較列 為NUll 或 值不存在 文檔 吸收進來文兢。

  3. 假設 源表(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í)行結果如下

image
](https://upload-images.jianshu.io/upload_images/20814339-603f911e7a683c7e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看出執(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"
       }
  }
])

在這里插入圖片描述
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涕侈,隨后出現(xiàn)的幾起案子沪停,更是在濱河造成了極大的恐慌,老刑警劉巖驾凶,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牙甫,死亡現(xiàn)場離奇詭異掷酗,居然都是意外死亡,警方通過查閱死者的電腦和手機窟哺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門泻轰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人且轨,你說我怎么就攤上這事浮声。” “怎么了旋奢?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵泳挥,是天一觀的道長。 經(jīng)常有香客問我至朗,道長屉符,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任锹引,我火速辦了婚禮矗钟,結果婚禮上,老公的妹妹穿的比我還像新娘嫌变。我一直安慰自己吨艇,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布腾啥。 她就那樣靜靜地躺著东涡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倘待。 梳的紋絲不亂的頭發(fā)上疮跑,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音延柠,去河邊找鬼祸挪。 笑死,一個胖子當著我的面吹牛贞间,可吹牛的內容都是我干的贿条。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼增热,長吁一口氣:“原來是場噩夢啊……” “哼整以!你這毒婦竟也來了?” 一聲冷哼從身側響起峻仇,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤公黑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凡蚜,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡人断,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了朝蜘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恶迈。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谱醇,靈堂內的尸體忽然破棺而出暇仲,到底是詐尸還是另有隱情,我是刑警寧澤副渴,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布奈附,位于F島的核電站,受9級特大地震影響煮剧,放射性物質發(fā)生泄漏斥滤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一轿秧、第九天 我趴在偏房一處隱蔽的房頂上張望中跌。 院中可真熱鬧,春花似錦菇篡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凸克,卻和暖如春议蟆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萎战。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工咐容, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚂维。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓戳粒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虫啥。 傳聞我的和親對象是個殘疾皇子蔚约,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容