mongo對文件中陣列進行過濾的三種方法

金色旭光發表於2024-10-12

前言

在mongo中資料型別有很多種,常見的包括:

資料型別 例子 描述
String { "x" : "foot" } 字串。儲存資料常用的資料型別。在 MongoDB 中,UTF-8 編碼的字串才是合法的。
Integer { "x" : 1 } 整型數值。用於儲存數值。根據你所採用的伺服器,可分為 32 位或 64 位。
Object { "x" : { "y" : "foot" } } 用於內嵌文件
Array { "x" : [ "a" , "b" ] } 用於將陣列或列表或多個值儲存為一個鍵。

有一種很常見的查詢,就是過濾陣列中的一些資料,只返回符合要求的資料。資料如下,將下面travel中的vehicle=train的記錄保留,過濾掉其他的元素,並返回整個文件。

{
    "name": "tom", 
    "travel": [
        {
            "vehicle" : "train",
            "city" : "北京"
        },
        {
            "vehicle" : "plane",
            "city" : "上海"
        }, 
        {
            "vehicle" : "train",
            "city" : "深圳"
        }
    ]
}

想要實現陣列的過濾有三種方法,包括:

  1. 聚合查詢 使用$unwindtravel陣列打散,獲取結果集後用$match篩選符合條件的資料,最後使用$group進行聚合獲取最終結果集
  2. 聚合查詢 使用$match過濾符合條件的根文件結果集,然後使用$project返回對應欄位的同時,在travel陣列中使用$filter進行內部過濾,返回最終結果集
  3. 普通查詢 先篩選記錄,然後透過投影查詢過濾陣列

下面來分析這三種方法能否實現需求。

新增資料

假設有兩條記錄,每條記錄是一個人的資訊,包括姓名、職業、旅遊過的城市。旅遊過的城市是一個陣列,包含城市的名字以及交通工具。

db.test.insertOne({
    "uid" : "1000001",
    "name" : "zhangsan",
    "job": "coder",
    "travel" : [ 
        {
            "vehicle" : "train",
            "city" : "北京"
        }, 
        {
            "vehicle" : "plane",
            "city" : "上海"
        }, 
        {
            "vehicle" : "train",
            "city" : "深圳"
        }
    ]
})
db.test.insertOne({

    "uid" : "1000002",
    "name" : "lisi",
    "job": "coder",
    "travel" : [ 
        {
            "vehicle" : "plane",
            "city" : "北京"
        }, 
        {
            "vehicle" : "car",
            "city" : "上海"
        }, 
        {
            "vehicle" : "train",
            "city" : "深圳"
        }
    ]
})
db.test.find()
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  job: 'coder',
  travel: 
   [ { vehicle: 'train', city: '北京' },
     { vehicle: 'plane', city: '上海' },
     { vehicle: 'train', city: '深圳' } ] }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  job: 'coder',
  travel: 
   [ { vehicle: 'plane', city: '北京' },
     { vehicle: 'car', city: '上海' },
     { vehicle: 'train', city: '深圳' } ] }

驗證三種方法

需求說明

現在的目標是:篩選的出所有記錄中透過火車去旅遊的城市,也就是travel陣列中vehicle=train的記錄,過濾掉非目標記錄。

方法一

方法一:使用$unwindtravel陣列打散,獲取結果集後用match篩選符合條件的資料,最後使用$group進行聚合獲取最終結果集。

db.getCollection('test').aggregate(
    [
        {   
            $unwind: "$travel" 
        },
        { 
            $match : {
                "job":"coder", 
                "travel.vehicle": "train" 
            } 
        },
        { 
            $group : { 
                "_id" : "$uid", 
                "travel": { $push: "$travel" } 
            } 
        } 
    ]
)

結果:

{ _id: '1000002', travel: [ { vehicle: 'train', city: '深圳' } ] }
{ _id: '1000001', travel: [ { vehicle: 'train', city: '北京' }, { vehicle: 'train', city: '深圳' } ] }

分析:

unwind 可以將一個陣列拆分,例如unwind的效果如下:

{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  job: 'coder',
  travel: { vehicle: 'train', city: '北京' } }
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  job: 'coder',
  travel: { vehicle: 'plane', city: '上海' } }
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  job: 'coder',
  travel: { vehicle: 'train', city: '深圳' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  job: 'coder',
  travel: { vehicle: 'plane', city: '北京' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  job: 'coder',
  travel: { vehicle: 'car', city: '上海' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  job: 'coder',
  travel: { vehicle: 'train', city: '深圳' } }

然後透過match篩選出符合條件的資料

{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  job: 'coder',
  travel: { vehicle: 'train', city: '北京' } }
{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  job: 'coder',
  travel: { vehicle: 'train', city: '深圳' } }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  job: 'coder',
  travel: { vehicle: 'train', city: '深圳' } }

最後透過group進行聚合,以_id為聚合依賴,合併相同_id的資料。

總結:

這種方法是能夠達到過濾陣列的要求,但是有一個問題,拆分陣列比較簡單,想要再合併起來就不容易了。group只能以某一個變數為基準聚合,其他變數都會丟失。比如最後的結果只保留了_id和travel,其他變數都丟失了。

方法二

方法二:使用$match過濾符合條件的根文件結果集,然後使用$project返回對應欄位的同時,在travel陣列中使用$filter進行內部過濾,返回最終結果集

db.getCollection('test').aggregate(
    [
        { 
            $match : { "job": "coder" } 
        },
        {
            $project: {
                "uid": 1,
                "name": 1,
                "travel": {
                    $filter: {
                        input: "$travel",
                        as: "item",
                        cond: { $eq : ["$$item.vehicle","train"] }
                    }
                }
            }
        }
    ]
)

結果分析:

{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  travel: [ { vehicle: 'train', city: '北京' },{ vehicle: 'train', city: '深圳' } ] }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  travel: [ { vehicle: 'train', city: '深圳' } ] }

分析:

mongo中查詢分為兩種:普通查詢和高階查詢。高階查詢包括聚合查詢,用aggregate關鍵字實現。

MongoDB的聚合管道將MongoDB文件在一個管道處理完畢後將結果傳遞給下一個管道處理。管道操作是可以重複的。

這裡我們介紹一下聚合框架中常用的幾個操作:

  • $project:修改輸入文件的結構。可以用來重新命名、增加或刪除域,也可以用於建立計算結果以及巢狀文件。
  • $match:用於過濾資料,只輸出符合條件的文件。$match使用MongoDB的標準查詢操作。
  • $limit:用來限制MongoDB聚合管道返回的文件數。
  • $skip:在聚合管道中跳過指定數量的文件,並返回餘下的文件。
  • $unwind:將文件中的某一個陣列型別欄位拆分成多條,每條包含陣列中的一個值。
  • $group:將集合中的文件分組,可用於統計結果。
  • $sort:將輸入文件排序後輸出。
  • $geoNear:輸出接近某一地理位置的有序文件。

這裡首先使用match過濾所有job=coder,然後使用project修改輸出的結構。在project中使用了filter來過濾陣列中的元素。

filter的定義如下:

根據指定條件選擇要返回的陣列的子集。返回僅包含與條件匹配的那些元素的陣列。返回的元素按原始順序。

$filter 具有以下語法:

{ $filter: { input: <array>, as: <string>, cond: <expression> } }
領域 規格
input 解析為陣列的表示式。
as 可選的。代表陣列中每個單獨元素的變數名稱input。如果未指定名稱,則變數名稱預設為this
cond 該表示式可解析為布林值,該布林值用於確定輸出陣列中是否應包含元素。該表示式input使用在中指定的變數名稱分別引用陣列的每個元素as。

https://mongodb.net.cn/manual/reference/operator/aggregation/filter/

在cond將vehicle=train的元素留下,排除其他元素。

總結:

這種方法可以完成查詢目標,既可以過濾掉陣列中的元素,也可以返回完整的文件。

方法三

方法三:

透過投影查詢,先選擇符合條件的記錄,在透過使用投影運算子,需要返回的欄位,以及排除特定的欄位。

db.test.find(
      {
         job: "coder"
      }, 
      {  
          uid: 1, 
          name: 1, 
          travel: {
             $filter: {
                input: "$travel",
                as: "item",
                cond: { $eq : ["$$item.vehicle","train"] }
             } 
          } 
      }
)

結果:

{ _id: ObjectId("6708d3e646d2075ca11e88ce"),
  uid: '1000001',
  name: 'zhangsan',
  travel: 
   [ { vehicle: 'train', city: '北京' },
     { vehicle: 'train', city: '深圳' } ] }
{ _id: ObjectId("6708d3f646d2075ca11e88cf"),
  uid: '1000002',
  name: 'lisi',
  travel: [ { vehicle: 'train', city: '深圳' } ] }

分析:

什麼是投影查詢?

在MongoDB中,投影查詢是一種查詢操作,用於選擇性地返回文件中的欄位。透過使用投影運算子,我們可以指定需要返回的欄位,以及是否要排除特定的欄位。

投影查詢語法如下所示:

db.collection.find({ <query> }, { <projection> })

其中, 是一個查詢表示式,用於篩選滿足條件的文件。 是一個可選引數,用於指定要返回的欄位。

在projection中保留欄位、排除欄位、選擇或排除陣列中的特定元素。利用選擇或排除陣列中的特定元素的特性也可以達到目的。

例如:

如果我們只想返回每個文件中的第一個標籤,我們可以這樣做:

db.products.find({}, { tags: { $slice: 1 } })

在本篇中透過filter方法來過濾陣列,保留符合條件的元素。

總結:

該方法能夠完成查詢目標,並且是一種簡潔的實現,普通查詢複雜度低,而且沒有太多關鍵字的使用。

參考文件

https://geek-docs.com/mongodb/mongodb-questions/393_mongodb_mongo_query_with_projection.html

https://segmentfault.com/a/1190000016629733

https://mongodb.net.cn/manual/reference/operator/aggregation/filter/

https://blog.csdn.net/weixin_44009447/article/details/115479348

相關文章