在mongodb中匹配的OR条件的百分比

时间:2014-04-23 11:21:30

标签: php mongodb mongodb-query aggregation-framework mongodb-php

我已按以下格式获取数据..

{
  "_id" : ObjectId("534fd4662d22a05415000000"),
  "product_id" : "50862224",
  "ean" : "8808992479390",
  "brand" : "LG",
  "model" : "37LH3000",
  "features" : [{
      {
      "key" : "Screen Format",
      "value" : "16:9",
    }, {
      "key" : "DVD Player / Recorder",
      "value" : "No",
    }, 
      "key" : "Weight in kg",
      "value" : "12.6",
    }
    ... so on
    ]
}

我需要将一种产品的功能与其他产品进行比较,并根据功能匹配百分比将结果划分为不同的类别(100%匹配,50-99%匹配)。

我最初的想法是为每个功能准备一个动态查询或条件,并在php中执行百分比,但这意味着mongodb将返回我甚至那些只有1个功能匹配的产品。而且我认为几乎所有类别的产品都有一些共同的特性,所以我担心我可能会在php中使用很多产品。

我基本上有两个问题。

  1. 还有其他方法吗?
  2. 我使用的数据结构是否足以支持我正在寻找的功能,或者我应该考虑更改它

2 个答案:

答案 0 :(得分:2)

我假设您想要将其余的集合与给定的产品进行比较,这是一个集合的教科书示例:

lookingat = db.products.findOne({product_id:'50862224'})
matches = db.products.aggregate([
    { $unwind: '$features' },
    { $match: { features: { $in: lookingat.features }}},
    { $group: { _id: '$product_id', matchedfeatures: { $sum:1 }}},
    { $sort: { matchedfeatures: -1 }},
    { $limit: 5 },
    { $project: { _id:0, product_id: '$_id',
                  pctmatch: { $multiply: [ '$matchedfeatures',
                                           100/lookingat.features.length ]}
      }}
])

从具有6个功能的集合中的产品的角度简要介绍一下,并将其与具有4个功能的目标产品('lookingat')进行比较,其中3个匹配:

  1. $ unwind将包含6个功能的1个文档转换为6个不同的文档,每个文档具有1个功能
  2. $ match在目标的要素数组中查找该要素(请注意,只有两个文档具有相同的字段名称和值(按相同顺序)才“相等”),丢弃不匹配的3个,并传递
  3. 的3
  4. $ group使用这3个匹配的文档并生成一个新文档,告诉您有3个文档与该product_id匹配
  5. $ sort和$ limit为您提供最相关的结果,并留下您关注的所有1个功能匹配
  6. $ project允许您将$ group中的_id重命名为product_id,并将匹配功能的数量计算为百分比(我们通过识别计算中3个项中的2个是常量来避免$ divide操作可以分为JS)

答案 1 :(得分:2)

你的解决方案确实应该是MongoDB特定的,否则你最终会在客户端进行计算和可能的匹配,这对性能不会有好处。

当然,您真正想要的是在服务器端进行处理的方法:

db.products.aggregate([

    // Match the documents that meet your conditions
    { "$match": {
        "$or": [
            { 
                "features": { 
                    "$elemMatch": {
                       "key": "Screen Format",
                       "value": "16:9"
                    }
                }
            },
            { 
                "features": { 
                    "$elemMatch": {
                       "key" : "Weight in kg",
                       "value" : { "$gt": "5", "$lt": "8" }
                    }
                }
            },
        ]
    }},

    // Keep the document and a copy of the features array
    { "$project": {
        "_id": {
            "_id": "$_id",
            "product_id": "$product_id",
            "ean": "$ean",
            "brand": "$brand",
            "model": "$model",
            "features": "$features"
        },
        "features": 1
    }},

    // Unwind the array
    { "$unwind": "$features" },

    // Find the actual elements that match the conditions
    { "$match": {
        "$or": [
            { 
               "features.key": "Screen Format",
               "features.value": "16:9"
            },
            { 
               "features.key" : "Weight in kg",
               "features.value" : { "$gt": "5", "$lt": "8" }
            },
        ]
    }},

    // Count those matched elements
    { "$group": {
        "_id": "$_id",
        "count": { "$sum": 1 }
    }},

    // Restore the document and divide the mated elements by the
    // number of elements in the "or" condition
    { "$project": {
        "_id": "$_id._id",
        "product_id": "$_id.product_id",
        "ean": "$_id.ean",
        "brand": "$_id.brand",
        "model": "$_id.model",
        "features": "$_id.features",
        "matched": { "$divide": [ "$count", 2 ] }
    }},

    // Sort by the matched percentage
    { "$sort": { "matched": -1 } }

])

因为您知道正在应用的 $or 条件的“长度”,那么您只需要找出“features”数组中有多少元素符合这些条件。这就是管道中第二个$匹配的全部内容。

一旦计算完毕,您只需将 $or 传递的条件数除以。这里的美妙之处在于,现在你可以做一些有用的事情,比如按相关性排序,然后甚至“点”结果服务器端。

当然,如果您想要对此进行一些额外的“分类”,您需要做的就是在管道的末尾添加另一个 $project 阶段:

    { "$project": {
        "product_id": 1
        "ean": 1
        "brand": 1
        "model": 1,
        "features": 1,
        "matched": 1,
        "category": { "$cond": [
            { "$eq": [ "$matched", 1 ] },
            "100",
            { "$cond": [ 
                { "$gte": [ "$matched", .7 ] },
                "70-99",
                { "$cond": [
                   "$gte": [ "$matched", .4 ] },
                   "40-69",
                   "under 40"
                ]} 
            ]}
        ]}
    }}

或类似的东西。但是$cond运营商可以在这里为您提供帮助。

架构应该没问题,因为你可以在feature列中的条目的“key”和“value”上有一个复合索引,这对于查询来说应该可以很好地扩展。

当然,如果您确实需要更多内容,例如分面搜索和结果,您可以查看Solr或弹性搜索等解决方案。但是完全实现这一点在这里会有点冗长。