天天看點

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

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"
       }
  }
])
           

傳回的執行結果如下:

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

分析查詢語句和結果,回扣

$lookup

定義,可以将上面的處理過程,描述如下:

集合order

中逐個擷取文檔處理,拿到一個文檔後,會根據

localField

周遊

被 Join的

inventory集合

(from: “inventory”),看

inventory集合文檔

foreignField

值是否與之

相等

如果相等,就把

符合條件的inventory文檔

整體内嵌到

聚合架構新生成的文檔中

,并且

新key 統一命名為 inventory_docs

考慮到符合條件的文檔

不唯一

,這個

Key對應的Value是個數組形式

原集合中Key

對應的值為

Null值

不存在

時,需特别小心。

說明

在以下的說明中,為描述友善,将

from對應的集合

定義為

被join集合

待聚合的表

成為

源表

localField

foreignField

對應的Key 定義為

比較列

  1. 這個示例中,一共輸出了三個文檔,在沒有再次聚合(

    $match

    )的條件下,這個

    輸出文檔數量

    以輸入文檔的數量

    來決定的(由order來決定),而不是以被Join的集合(inventory)文檔數量決定。
  2. 在此需要

    特别強調

    的是輸出的第三個文檔。在源庫中原文檔沒有要比較的列(

    即item值不存在,既不是Null值,也不是值為空

    ),此時 和

    被Join 集合

    比較,如果

    被Join集合中 比較列 也恰好 為NUll 或 不存在的值,此時,判斷相等

    ,即會把 被Join集合中 比較列 為NUll 或 值不存在 文檔 吸收進來。
  3. 假設 源表(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"
       }
  }
])
           

此時檢視結果

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

查詢出的結果也由之前的

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" : [ ]} }
])
           

執行結果如下

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

可以看出執行結果隻有一個文檔。這個文檔表明的含義是:訂單中有這個商品,但是庫存中沒有這個商品。

$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"
   }
])
           

結果如下

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)
db.getCollection('orders').aggregate([
   {
      $unwind: "$specs"
   },
   {
      $lookup:
         {
            from: "inventory",
            localField: "specs",
            foreignField: "size",
            as: "inventory_docs"
        }
   }
])
           

查詢顯示結果如下:

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

輸出文檔中的

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"}
    }
])
           

結果如下

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

再上面的前提下,隻輸出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}
   }
])
           

結果如下

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

最後一道題,在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

的值不同外,其他的值都是相同的.

評論區提問解答

問題一:

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

在表A中有一個字段,類型為數組,裡面存放的表B的ID字段。是以一條表A的資料對應複數條表B的資料。現在我每次隻查詢單條表A的資料,想通過

$unwind和$lookup

關聯查詢,但傳回的結果是一個數組,數組裡面除了插入的表B的資料不同之外其他的資料都是重複的。

希望達成的效果

是最終隻傳回一條表A的資料,這條資料中的那個數組裡是我查詢并插入的所有表B的資料,這樣結果就不會有太多的重複,但我不知道該如何實作這樣的效果?

問題二

MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

第二個問題是,使用

$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"}
])
           
MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

執行下面查詢得到

db.getCollection('a').aggregate([
    {$unwind: "$a4"}, // 這一行處理後的結果 給一行處理
    {$match : {"a4" : "002"}}
])
           
MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

執行下面的查詢得到

db.getCollection('a').aggregate([
    {$unwind: "$a4"}, // 這一行處理後的結果 給一行處理
    {$match : {"a4" : "002"}}, // 這一行處理後的結果 給一行處理
    {$project: {a2:1}}
])
           
MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

執行下面的查詢得到

db.getCollection('a').aggregate([
    {$unwind: "$a4"},
    {
     $lookup:
       {
         from: "b",
         localField: "a4",
         foreignField: "id",
         as: "new"
       }
  }
])
           
MongoDB中的聚合管道($lookup多表關聯查詢、$unwind、$match、$project)

謝謝你閱讀到了最後~

期待你關注、收藏、評論、點贊~

讓我們一起 變得更強

推薦閱讀

mongodb、mongoose相關

MongoDB 常用指令彙總 該有的都有