【问题标题】:Return zero count where data does not exist在数据不存在的情况下返回零计数
【发布时间】:2014-12-28 18:38:24
【问题描述】:

我的 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 的事情...?

【问题讨论】:

  • 您的文档以 {{ ! 开头。是一个文档还是多个文档?
  • @Disposer 多个文档..
  • 我试过你的查询,得到了这个结果:{ "result" : [ { "_id" : { "type" : "murder", "year" : "2013" }, "计数”:1 },{“_id”:{“类型”:“非法贸易”,“年份”:“2014”},“计数”:1 },{“_id”:{“类型”:“非法贸易”, “年”:“2013”​​},“计数”:2 }],“确定”:1 }
  • 是的,它有效。但我希望结果也包括 "count": 0 项目。例如,结果还应该包含{ "_id" : { "type" : "murder", "year" : "2014" }, "count" : 0 }...我想知道是否有办法做到这一点? @Disposer
  • 它怎么可能包含数据库中不存在的组合?你想让 mongodb 猜出所有可能的组合吗?

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


【解决方案1】:

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

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

因此,在使用 MongoDB 时创建“错误集”的任务被降级到客户端(并且仅从“客户端”的角度考虑这对数据库服务器来说是一个单独的进程)操作。所以你基本上得到了“结果集”和“空白集”并“合并”了结果。

不同的语言方法各不相同,但这里有一个 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”中“谋杀”的计数,因为没有。但这基本上归结为合并结果。

“合并”基本上是使用“哈希/映射/字典”(无论您的术语是什么),“年份”和“类型”的组合键。因此,您只需使用该结构,在它不存在的地方添加键或在它存在的地方增加该键的“计数”值。这是一个古老的操作,基本上是所有聚合技术的基础。

这里要做的巧妙的小事情(不是你需要使用它)是使用nedb,这是一个不错的小模块,允许在内存或其他上使用MongoDB“like”操作自包含数据文件。把它想象成 SQLite 到 SQL RDBMS 的操作。只是对完整功能稍微轻了一点。

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

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

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

方法是不同的,但这又是这里哲学的一部分。 MongoDB 将“加入”活动与应用程序架构的不同部分相关联,以保持其自己的服务器特定操作更高效,并且主要针对分片集群。 “Joining”或“Hash Merge”是一种“代码”功能,可由数据库服务器以外的其他基础设施处理。

【讨论】:

    猜你喜欢
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-21
    • 2020-11-23
    • 2020-03-20
    相关资源
    最近更新 更多