如何连接所有值并在Mongodb中查找特定的子字符串?

时间:2014-05-08 14:29:13

标签: mongodb mapreduce mongodb-query aggregation-framework

我有这样的json文档:

{
  "A": [
    {
      "C": "abc",
      "D": "de"
    },
    {
      "C": "fg",
      "D": "hi"
    }
  ]
}

我会检查" A"包含字符串 ef 或不包含。 首先连接所有值 abcdefghi 然后搜索 ef

在XML中,XPATH会是这样的: //A[contains(., 'ef')]

Mongodb中是否有类似的查询?

1 个答案:

答案 0 :(得分:1)

对于此类搜索,所有选项都非常糟糕,但您可以采取一些方法。请注意,虽然这里的最终案例可能是最好的解决方案,但我提供了选项以说明问题。

如果数组“A”中的键一致定义且始终包含数组,则您将按以下方式搜索:

db.collection.aggregate([
    // Filter the documents containing your parts
    { "$match": {
        "$and": [
            { "$or": [
                { "A.C": /e/ },
                { "A.D": /e/ } 
            ]},
            {"$or": [
                { "A.C": /f/ },
                { "A.D": /f/ }
            ]}
        ]
    }},

    // Keep the original form and a copy of the array
    { "$project": { 
        "_id": { 
            "_id": "$_id", 
            "A": "$A" 
        },
        "A": 1 
    }},

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

    // Join the two fields and push to a single array
    { "$group": {
         "_id": "$_id",
         "joined": { "$push": {
             "$concat": [ "$A.C", "$A.D" ]
         }}
    }},

    // Copy the array
    { "$project": {
        "C": "$joined",
        "D": "$joined"
    }},

    // Unwind both arrays
    { "$unwind": "$C" },
    { "$unwind": "$D" },

    // Join the copies and test if they are the same
    { "$project": {
        "joined": { "$concat": [ "$C", "$D" ] },
        "same": { "$eq": [ "$C", "$D" ] },
    }},

    // Discard the "same" elements and search for the required string
    { "$match": {
        "same": false,
        "joined": { "$regex": "ef" }
    }},

    // Project the origial form of the matching documents
    { "$project": {
        "_id": "$_id._id",
        "A": "$_id.A"
    }}
])

除了可怕的 $regex 匹配之外,为了让字段“加入”以便再次按顺序搜索字符串,需要进行一些操作。另请注意,此处可能出现的反向连接可能会产生误报。目前没有简单的方法可以避免反向连接或以其他方式对其进行过滤,因此需要考虑。

另一种方法是基本上通过任意JavaScript运行所有内容。 mapReduce方法可以作为您的工具。在这里你可以稍微宽松一下,可以包含在“A”和尝试中的数据类型,以便将一些更多的条件匹配与尝试联系起来以减少您正在处理的文件:

db.collection.mapReduce(
    function () {

      var joined = "";

      if ( Object.prototype.toString.call( this.A ) === '[object Array]' ) {
        this.A.forEach(function(doc) {
          for ( var k in doc ) {
            joined += doc[k];
          }
        });
      } else {
        joined = this.A;  // presuming this is just a string
      }

      var id = this._id;
      delete this["_id"];

      if ( joined.match(/ef/) )
        emit( id, this  );

    },
    function(){},    // will not reduce
    { 
        "query": {
            "$or": [
                { "A": /ef/ },
                { "$and": [
                    { "$or": [
                        { "A.C": /e/ },
                        { "A.D": /e/ } 
                    ]},
                    {"$or": [
                        { "A.C": /f/ },
                        { "A.D": /f/ }
                    ]}
                ] }
            ]
        },
        "out": { "inline": 1 }
    }
);

因此,您可以使用任意逻辑来搜索包含的对象。这个只是区分“数组”和假设否则是一个字符串,允许查询的其他部分首先只搜索匹配的“字符串”元素,这是一个“短路”评估。

但实际上在最后,最好的方法是简单地将数据存在于您的文档中,并且在更新文档内容时必须自己维护:

{
  "A": [
    {
      "C": "abc",
      "D": "de"
    },
    {
      "C": "fg",
      "D": "hi"
    }
  ],
  "search": "abcdefghi"
}

所以这仍然会调用 $regex 类型查询的可怕用法,但至少可以避免(或者转而写入文档)“加入”元素的开销为了影响搜索您想要的字符串。

这最终导致的是一个“完整的”文本搜索解决方案,这意味着此时的外部搜索解决方案与MongoDB中的文本搜索工具相对,可能是您最佳的性能选项。

使用“预先存储”方法创建“已加入”字段或支持其他方式(Solr是一种可以执行此操作的解决方案)在索引文档内容时创建的此文本索引中具有“计算字段”

无论如何,这些都是问题的方法和一般要点。这不是XPath搜索,在这个意义上不是它们对整个集合的一些“XPath like”视图,因此您最适合将数据结构化为能够提供最佳性能的方法。

所有这些都说明了,你的示例是一个相当人为的例子,如果你有一个实际的用例“like”这个,那么这个实际案例可能会成为一个非常有趣的问题确实。实际案例通常与人为案例有不同的解决方案。但现在你需要考虑一下。