MongoDB - 使用聚合框架或mapreduce匹配文档中的字符串数组(配置文件匹配)

时间:2013-04-30 23:47:20

标签: mongodb mapreduce aggregation-framework

我正在构建一个可以比作约会应用程序的应用程序。

我有一些文件的结构如下:

  

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

我已经考虑过将此作为map reduce或使用聚合框架。

无论哪种方式,“结果”都会持久保存到集合中(根据上面的“结果”)

我的问题是两者中哪一个更适合? 我会从哪里开始实现这个?

修改

简而言之,该模型不易改变 这不像传统意义上的“个人资料”。

我基本上要做的事情(在伪代码中)是这样的:

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!

显然那种操作非常慢!

值得一提的是,这些数据永远不会显示,它实际上只是一个用于“匹配”的字符串值

2 个答案:

答案 0 :(得分:9)

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($ unwind)group by fieldValue一个匹配的配置文件_id和名称的数组,计算每个fieldValue累积的匹配数($ group),然后排除只有一个匹配它的配置文件的匹配。

答案 1 :(得分:-1)

首先,在区分两者时,MongoDB的聚合框架基本上只是mapreduce,但更有限,因此它可以提供更直接的界面。据我所知,聚合框架除了一般的mapreduce之外不能做任何事情。

考虑到这一点,问题就变成了:您的转换是可以在聚合框架中建模的,还是需要回归到更强大的mapreduce。

如果我理解你要做什么,我认为如果你稍微改变你的模式,聚合框架是可行的。模式设计是Mongo最棘手的事情之一,在决定如何构建数据时需要考虑很多事情。尽管对你的申请知之甚少,但我还是会出去做一个建议。

具体来说,我建议您将fieldValues子文档的结构更改为:

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

即,将多值属性存储在数组中。这将允许您利用聚合框架的$unwind运算符。 (参见the example in the Mongo documentation。)但是,根据您要完成的任务,这可能适合也可能不适合。

退一步说,你可能觉得不适合使用聚合框架 Mongo的mapreduce函数。它们的使用具有性能影响,将它们用于应用程序的核心业务逻辑可能不是一个好主意。一般来说,它们的预期用途似乎只是偶尔或 ad-hoc 查询,只是为了深入了解一个人的数据。所以,你可能最好从一个“真正的”mapreduce框架开始。也就是说,我听说过聚合框架在cron工作中用于定期创建核心业务数据的情况。