返回零计数,其中数据不存在

时间:2014-12-28 18:38:25

标签: javascript node.js mongodb mongodb-query aggregation-framework

我的mongodb数据集中的文档遵循以下格式

[ 
  {
    "_id": xxxxxxxxx,
    "crime_type": "illegal_trade",
    "crime_year": "2013",
    "location": "Kurunegala"
  },
  {
    "_id": xxxxxxxxx,
    "crime_type": "illegal_trade",
    "crime_year": "2013",
    "location": "Colombo"
  },
  {
    "_id": xxxxxxxxx,
    "crime_type": "illegal_trade",
    "crime_year": "2014",
    "location": "Kandy"
  },
  {
    "_id": xxxxxxxxx,
    "crime_type": "murder",
    "crime_year": "2013",
    "location": "Kadawatha"
  }
]

当我运行此聚合操作时,

db.collection.aggregate(
   [
     { $group : { _id : {type: "$crime_type", year: "$crime_year"}, count: { $sum: 1 } } }
   ]
)

结果仅包含count > 0

的项目

示例_id : {type: "murder", year: "2014"}的结果,其中count = 0不会包含在结果中。

我的问题是, 我应该如何更改我的查询,以便这些count = 0项目也在搜索结果中?

换句话说,如何使用mongodb做某些事情this

1 个答案:

答案 0 :(得分:2)

基本上你要求的是数据中没有的结果,所以如果不存在该组合键,那么你返回0的计数。事实上,没有数据库系统“真正”做到这一点,但有让它看起来像这样的方法正在发生。但这也意味着要了解真正发生的事情。

确实,SQL的方法是从不同的值进行预期键的子查询,然后将其“连接”到现有数据集,以便为分组累积创建“误报”。这是那里的一般方法,但当然存在基本的“连接”概念,由于可伸缩性的原因,MongoDB不支持该概念。那里有完全不同的论点,只是接受MongoDB不会在它自己的服务器架构上加入,并且可能永远不会。

因此,在使用MongoDB时创建“false set”的任务被降级到客户端(并且仅考虑“客户端”是对数据库服务器的单独过程)操作。所以你基本上得到“结果集”和“空白集”并“合并”结果。

不同的语言方法各不相同,但这里是node.js的有效列表:

var async = require('async'),
    mongo = require('mongodb'),
    MongoClient = mongo.MongoClient,
    DataStore = require('nedb'),
    combined = new DataStore();

var data = [
  {
    "crime_type": "illegal_trade",
    "crime_year": "2013",
    "location": "Kurunegala"
  },
  {
    "crime_type": "illegal_trade",
    "crime_year": "2013",
    "location": "Colombo"
  },
  {
    "crime_type": "illegal_trade",
    "crime_year": "2014",
    "location": "Kandy"
  },
  {
    "crime_type": "murder",
    "crime_year": "2013",
    "location": "Kadawatha"
  }
];


MongoClient.connect('mongodb://localhost/test',function(err,db) {

  if (err) throw err;

  db.collection('mytest',function(err,collection) {
    if (err) throw err;

    async.series(
      [
        // Clear collection
        function(callback) {
          console.log("Dropping..\n");
          collection.remove({},callback);
        },

        // Insert data
        function(callback) {
          console.log("Inserting..\n");
          collection.insert(data,callback);
        },

        // Run parallel merge
        function(callback) {
          console.log("Merging..\n");
          async.parallel(
            [
              // Blank Distincts
              function(callback) {
                collection.distinct("crime_year",function(err,years) {
                  if (err) callback(err);
                  async.each( years, function(year,callback) {
                    collection.distinct("crime_type",function(err,types) {
                      if (err) callback(err);
                      async.each( types, function(type,callback) {
                        combined.update(
                          { "type": type, "year": year },
                          { "$inc": { "count": 0 } },
                          { "upsert": true },
                          callback
                        );
                      },callback);
                    });
                  },callback);
                });
              },

              // Result distincts
              function(callback) {
                collection.aggregate(
                  [
                    { "$group": {
                      "_id": {
                        "type": "$crime_type",
                        "year": "$crime_year"
                      },
                      "count": { "$sum": 1 }
                    }}
                  ],
                  function(err,results) {
                    async.each( results, function(result, callback) {
                      combined.update(
                        { "type": result._id.type, "year": result._id.year },
                        { "$inc": { "count": result.count } },
                        { "upsert": true },
                        callback
                      );
                    },callback);

                  }
                );
              }
            ],
            function(err) {
              callback(err);
            }
          )

        },

        // Retrieve result
        function(callback) {
          console.log("Fetching:\n");
          combined.find({},{ "_id": 0 }).sort(
            { "year": 1, "type": 1 }).exec(function(err,results) {
            if (err) callback(err);
            console.log( JSON.stringify( results, undefined, 4 ) );
            callback();
          });
        }
      ],
      function(err) {
        if (err) throw err;
        db.close();
      }
    )

  });

});

这将返回一个结果,不仅“组合”分组键的结果,而且在“2014”年中还包含“谋杀”的0条目:

[
    {
        "type": "illegal_trade",
        "year": "2013",
        "count": 2
    },
    {
        "type": "murder",
        "year": "2013",
        "count": 1
    },
    {
        "type": "illegal_trade",
        "year": "2014",
        "count": 1
    },
    {
        "type": "murder",
        "year": "2014",
        "count": 0
    }
]

因此,请考虑操作的内容,主要是在“合并”下的代码的“并行”部分,因为这是节点发出所有查询的有效方式(可能还有很多)一切都在同一时间。

第一部分为了获得没有计数的“空白”结果本质上是一个双循环操作,其中的要点是获得“年”和“类型”中每一个的不同值。无论您使用此处所示的.distinct()方法,还是使用带有“光标”的.aggregate()方法进行输出和迭代,都取决于您拥有的数据量或您个人喜欢的数据。对于一个小集合,然后.distinct()可以很好地将结果存储在内存中。但我们希望为每个可能的配对创建“空白”或0计数条目,或者更重要的是包括那些在数据集中作为配对“不存在”的条目。

其次,在可能的情况下,并行地使用标准结果运行聚合结果。当然,这些结果不会返回“2014”中“谋杀”的计数,因为没有。但这基本上归结为合并结果。

“合并”基本上与“年”和“类型”的组合键“hash / map / dict”(无论你的术语是什么)一起使用。因此,您只需使用该结构,将键添加到不存在的位置,或者将该键上的“count”值递增到它所在的位置。这是一个古老的操作,基本上是所有聚合技术的基础。

在这里做的简洁小事(不是你需要使用它),是使用nedb,这是一个很好的小模块,允许在内存中使用MongoDB“like”操作或其他自包含的数据文件。可以把它想象成SQLite到SQL RDBMS操作。完整的功能只会轻一点。

这里的部分要点是“哈希合并”函数现在看起来像代码的常规MongoDB "upsert"操作。事实上,如果你有一个大的结果需要最终在服务器上的“结果集合”中,相同的代码基本上适用。

总的来说,这实际上是一个“连接”操作或者“填空”操作,具体取决于操作中“键”的总体大小和预期。 MongoDB服务器不会这样做,但是没有什么能阻止你将有效的自己的“数据层”写成你的最终应用程序和数据库之间的中间层。可以扩展这种分布式服务器模型,以便该服务级别执行这些“加入”操作。

用于获取要合并的数据的所有查询可以在正确的编码环境下有效地并行运行,因此虽然这可能看起来不像SQL方法那样简单,但它仍然可以非常有效并且有效地处理结果。

方法不同,但这又是哲学的一部分。 MongoDB将“加入”活动重新关联到应用程序体系结构的不同部分,以使其自身的服务器特定操作更加高效,并且主要针对分片集群。 “加入”或“哈希合并”是一种“代码”功能,可由除数据库服务器之外的其他基础结构处理。