簡體   English   中英

MongoDB-使用聚合框架或mapreduce來匹配文檔中的字符串數組(配置文件匹配)

[英]MongoDB - Use aggregation framework or mapreduce for matching array of strings within documents (profile matching)

我正在構建一個可以與約會應用程序比擬的應用程序。

我有一些結構如下的文件:

$ db.profiles.find()。pretty()

[
  {
    "_id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "fieldValues": [
      "favouriteColour|red",
      "food|pizza",
      "food|chinese"
    ]
  },
  {
    "_id": 2,
    "firstName": "Sarah",
    "lastName": "Jane",
    "fieldValues": [
      "favouriteColour|blue",
      "food|pizza",
      "food|mexican",
      "pets|yes"
    ]
  },
  {
    "_id": 3,
    "firstName": "Rachel",
    "lastName": "Jones",
    "fieldValues": [
      "food|pizza"
    ]
  }
]

我正在嘗試的是識別一個或多個fieldValues上彼此匹配的配置文件。

因此,在上面的示例中,我的理想結果如下所示:

<some query>

result:
[
  {
    "_id": "507f1f77bcf86cd799439011",
    "dateCreated": "2013-12-01",
    "profiles": [
      {
        "_id": 1,
        "firstName": "John",
        "lastName": "Smith",
        "fieldValues": [
          "favouriteColour|red",
          "food|pizza",
          "food|chinese"
        ]
      },
      {
        "_id": 2,
        "firstName": "Sarah",
        "lastName": "Jane",
        "fieldValues": [
          "favouriteColour|blue",
          "food|pizza",
          "food|mexican",
          "pets|yes"
        ]
      },

    ]
  },
  {
    "_id": "356g1dgk5cf86cd737858595",
    "dateCreated": "2013-12-02",
    "profiles": [
      {
        "_id": 1,
        "firstName": "John",
        "lastName": "Smith",
        "fieldValues": [
          "favouriteColour|red",
          "food|pizza",
          "food|chinese"
        ]
      },
      {
        "_id": 3,
        "firstName": "Rachel",
        "lastName": "Jones",
        "fieldValues": [
          "food|pizza"
        ]
      }
    ]
  }
]

我曾經考慮過將其作為地圖縮減或聚合框架來進行。

無論哪種方式,“結果”都將保留到集合中(根據上述“結果”)

我的問題是,哪兩個更合適? 從哪里開始實施呢?

編輯

簡而言之,無法輕松更改模型。
這與傳統意義上的“檔案”不同。

我基本上想做的事情(以偽代碼)是這樣的:

foreach profile in db.profiles.find()
  foreach otherProfile in db.profiles.find("_id": {$ne: profile._id})
    if profile.fieldValues matches any otherProfie.fieldValues
      //it's a match!

顯然,這種操作非常慢!

值得一提的是,此數據從不顯示,實際上只是一個字符串值,用於“匹配”

MapReduce將在單獨的線程中運行JavaScript,並使用您提供的代碼來發出和減少文檔的某些部分以聚集在某些字段上。 當然,您可以將練習匯總為每個“ fieldValue”。 聚合框架也可以做到這一點,但是會更快,因為聚合將在C ++的服務器上而不是在單獨的JavaScript線程中運行。 但是聚合框架返回的數據可能會超過16MB,在這種情況下,您將需要對數據集進行更復雜的分區。

但是似乎問題比這簡單得多。 您只想為每個配置文件查找其他配置文件與之共享哪些特定屬性-在不知道數據集大小和性能要求的情況下,我假設您在fieldValues上有一個索引,因此查詢效率很高在它上面,然后您可以通過以下簡單循環獲得所需的結果:

> db.profiles.find().forEach( function(p) { 
       print("Matching profiles for "+tojson(p));
       printjson(
            db.profiles.find(
               {"fieldValues": {"$in" : p.fieldValues},  
                                "_id" : {$gt:p._id}}
            ).toArray()
       ); 
 }  );

輸出:

Matching profiles for {
    "_id" : 1,
    "firstName" : "John",
    "lastName" : "Smith",
    "fieldValues" : [
        "favouriteColour|red",
        "food|pizza",
        "food|chinese"
    ]
}
[
    {
        "_id" : 2,
        "firstName" : "Sarah",
        "lastName" : "Jane",
        "fieldValues" : [
            "favouriteColour|blue",
            "food|pizza",
            "food|mexican",
            "pets|yes"
        ]
    },
    {
        "_id" : 3,
        "firstName" : "Rachel",
        "lastName" : "Jones",
        "fieldValues" : [
            "food|pizza"
        ]
    }
]
Matching profiles for {
    "_id" : 2,
    "firstName" : "Sarah",
    "lastName" : "Jane",
    "fieldValues" : [
        "favouriteColour|blue",
        "food|pizza",
        "food|mexican",
        "pets|yes"
    ]
}
[
    {
        "_id" : 3,
        "firstName" : "Rachel",
        "lastName" : "Jones",
        "fieldValues" : [
            "food|pizza"
        ]
    }
]
Matching profiles for {
    "_id" : 3,
    "firstName" : "Rachel",
    "lastName" : "Jones",
    "fieldValues" : [
        "food|pizza"
    ]
}
[ ]

顯然,您可以調整查詢以不排除已經匹配的配置文件(將{$gt:p._id}更改為{$ne:{p._id}}和其他調整。但是我不確定您會增加什么值從使用聚合框架或mapreduce獲得好處,因為這實際上並不是在其字段之一上聚合單個集合(根據您顯示的輸出格式判斷)。如果您對輸出格式的要求很靈活,那么肯定可以使用一個集合內置的聚合選項。

我確實檢查了一下,如果聚集在各個fieldValues周圍會是什么樣,這還不錯,如果您的輸出可以匹配以下內容,則可能會有所幫助:

> db.profiles.aggregate({$unwind:"$fieldValues"}, 
      {$group:{_id:"$fieldValues", 
              matchedProfiles : {$push:
               {  id:"$_id", 
                  name:{$concat:["$firstName"," ", "$lastName"]}}},
                  num:{$sum:1}
               }}, 
      {$match:{num:{$gt:1}}});
{
    "result" : [
        {
            "_id" : "food|pizza",
            "matchedProfiles" : [
                {
                    "id" : 1,
                    "name" : "John Smith"
                },
                {
                    "id" : 2,
                    "name" : "Sarah Jane"
                },
                {
                    "id" : 3,
                    "name" : "Rachel Jones"
                }
            ],
            "num" : 3
        }
    ],
    "ok" : 1
}

基本上是這樣說的:“對於每個由fieldValue組的fieldValue($ unwind)組,一個匹配的配置文件_id和名稱的數組,計算每個fieldValue累積的匹配項數($ group),然后排除只有一個與它匹配的配置文件。

首先,在區分兩者時,MongoDB的聚合框架基本上只是mapreduce,但受到限制,因此它可以提供更直接的接口。 據我所知,聚合框架除了通用mapreduce之外無能為力。

考慮到這一點,問題就變成了:您的轉換是可以在聚合框架中建模的,還是您需要依靠功能更強大的mapreduce?

如果我了解您要執行的操作,那么我認為如果稍微更改架構,使用聚合框架是可行的。 模式設計是Mongo最棘手的事情之一,在決定如何構造數據時,您需要考慮很多因素。 盡管對您的應用程序知之甚少,但我還是會不遺余力地提出建議。

具體來說,我建議將您構造fieldValues子文檔的方式更改為如下所示:

{
    "_id": 2,
    "firstName": "Sarah",
    "lastName": "Jane",
    "likes": {
        "colors": ["blue"],
        "foods": ["pizza", "mexican"],
        "pets": true
    }
}

即,將多值屬性存儲在數組中。 這將允許您利用聚合框架的$unwind運算符。 (請參閱Mongo文檔中的示例 。)但是,根據您要完成的工作,這可能合適也可能不合適。

但是,退后一步,您可能會發現使用聚合框架 Mongo的mapreduce函數不合適。 使用它們對性能有影響,將它們用於應用程序的核心業務邏輯可能不是一個好主意。 通常,它們的預期用途似乎是用於不頻繁或即席查詢,只是為了深入了解自己的數據。 因此,從“真正的” mapreduce框架開始可能會更好。 也就是說,我聽說過在cron作業中使用聚合框架定期創建核心業務數據的情況。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM