MongoDB高级查询 - 使用其他字段的值

时间:2014-07-18 09:17:57

标签: mongodb mapreduce aggregation-framework

我正在评估MongoDB,我希望看到它在查询方面的能力。

关于我的数据集,我可能需要使用字段的值来与其他字段进行比较。最好的解释方法是举个例子。

在下面的json中,我想 返回至少有一个年龄小于30且国家人口超过100万的人的文件。

{
  people: [
    { name: "Feyyaz", age: 28, country: "Turkiye" },
    { name: "Joseph", age: 25, country: "USA" },
    ...
  ],
  countries: [
    { name: "Turkiye", population: 75000000 },
    { name: "USA", population: 300000000 },
    ...
  ]
}

注意:这个例子完全由我组成,因为我的真实世界的例子要复杂得多。并且改变结构应该是最后的选择。

3 个答案:

答案 0 :(得分:3)

如果你可以使用Python来完成这项工作,你可以考虑使用查询语言ObjectPath 这使您可以在一行中完成作业:

$.people[@.age<30 and $.countries[@.name is @@.country].population > 100000000]

除了&#34; @@&#34;还没有实现 - 如果你想使用它,你可以在github页面上写一个功能请求

免责声明:计划在不久的将来将此语言与MongoDB集成,以便它可以利用MongoDB分布式功能。

答案 1 :(得分:1)

使用.find()的标准查询操作将无法按照您要求的方式将两个字段匹配在一起。您可以使用标准匹配条件获得“接近”结果,但实际上比较数组元素更为先进。

您正在寻找的“高级瑞士军刀”以MongoDB的aggregation framework形式出现。这不仅仅是“聚合”数据,因为它也是一般文档操作和评估的工具:

db.pop.aggregate([

  // Match possible documents to reduce work
  { "$match": {
    "people.age": { "$lt": 30 },
    "countries.population": { "$gt": 100000000 }
  }},

  // Test the conditions against the arrays      
  { "$project": {
    "people": 1,
    "countries": 1,
    "match": {
      "$anyElementTrue": {
        "$map": {
          "input": "$people",
          "as": "p",
          "in": {
            "$anyElementTrue": {
              "$map": {
                "input": "$countries",
                "as": "c",
                "in": {
                  "$and": [
                    { "$lt": [ "$$p.age",30 ] },
                    { "$gt": [ "$$c.population",100000000 ] },
                    { "$eq": [ "$$p.country", "$$c.name" ] }
                  ]
                }
              }
            }
          }
        }
      }
    }
  }},

  // Filter any documents that did not match
  { "$match": { "match": true }}
])

如果您正在“过滤”那些仅仅匹配结果,那么您可以稍微改变一下。我将分解$project个阶段,但你可以在一个阶段完成:

db.pop.aggregate([

  // Match possible documents to reduce work
  { "$match": {
    "people.age": { "$lt": 30 },
    "countries.population": { "$gt": 100000000 }
  }},

  // Filter the people array for matches
  { "$project": {
    "people": {
      "$setDifference": [
        { "$map": {
          "input": "$people",
          "as": "p",
          "in": {
            "$cond": [
              { "$and": [
                { "$lt": [ "$$p.age", 30 ] },
                {
                  "$anyElementTrue": {
                    "$map": {
                      "input": "$countries",
                      "as": "c",
                      "in": {
                        "$and": [
                          { "$gt": [ "$$c.population", 100000000 ] },
                          { "$eq": [ "$$p.country", "$$c.name" ] }
                        ]
                      }
                    }
                  }
                }
              ]},
              "$$p",
              false
            ]
          }
        }},
        [false]
      ]
    },
    "countries": 1
  }},

  // Discard any document that did not meet conditions
  { "$match": { "people": { "$ne": false } }},

  // Filter the countries to matching people
  { "$project": {
    "people": 1,
    "countries": {
      "$setDifference": [
        { "$map": {
          "input": "$countries",
          "as": "c",
          "in": {
            "$cond": [
              { "$and": [
                { "$gt": [ "$$c.population", 100000000 ] },
                {
                  "$anyElementTrue": {
                    "$map": {
                      "input": "$people",
                      "as": "p",
                      "in": {
                        "$eq": [ "$$p.country", "$$c.name" ]
                      }
                    }                    
                  }
                }
              ]},
              "$$c",
              false
            ]
          }
        }},
        [false]
      ]
    }
  }}
])

在第二种情况下,您将获得与此类不匹配的数组元素“已过滤”的文档:

{
    "_id" : ObjectId("53c8f1645117367f5ff2036c"),
    "people" : [
            {
                    "name" : "Joseph",
                    "age" : 25,
                    "country" : "USA"
            }
    ],
    "countries" : [
            {
                    "name" : "USA",
                    "population" : 300000000
            }
    ]
}

相当强大的东西。

另请参阅文档中的aggregation framework operators和其他聚合示例。

您也可以使用mapReduce执行类似的操作,但通常首选聚合框架,因为它是本机代码实现,而MongoDB mapReduce依赖于JavaScipt解释来运行。

答案 2 :(得分:0)

在NeilLunn的回答中提到FeyyazE的评论,实际上你也可以使用标准的javascript和非常经典且易于阅读的功能,如下所示:

function test1 (field) {return field <= 30;}
function test2 (field) {return field >= 100000000;}

var fct = function (array1, field1, pivot1, array2, field2, pivot2) {
    for (var key in array1) {
        if (test1(array1[key][field1])) {
            for (var key2 in array2) {
                if (array2[key2][pivot2] == array1[key][pivot1] && test2(array2[key2][field2])) {
                    return true;
                }
            }
        }
    }
    return false;
}

db.test.find({$where: "fct(
    this.people,
    'age',
    'country',
    this.countries,
    'population',
    'name'
)"});

但这真的需要一段时间才能让mongo进行评估。我在shell中尝试了一个小的100K文档集合,它花了... 3秒!所以也许你会更喜欢努力和难以阅读的脚本......