Mongodb 查找所有重叠间隔的计数

Mongodb 查找所有重叠间隔的计数,mongodb,mongodb-query,aggregation-framework,Mongodb,Mongodb Query,Aggregation Framework,我有这样的所有记录的开始时间和结束时间: { startTime : 21345678 endTime : 31345678 } 我试图找出所有冲突的数量。例如,如果有两条记录重叠,则冲突数为1。如果有三条记录,其中两条重叠,则冲突为1。如果有三条记录且所有三条记录重叠,则冲突为3,即[(X1,X2),(X1,X3),(X2,X3)] 作为一种算法,我考虑按开始时间对数据进行排序,对每个排序的记录检查结束时间,并查找开始时间小于结束时间的记录。这将是O(n2)时间。更好的方法是使用间隔

我有这样的所有记录的开始时间和结束时间:

{
 startTime : 21345678
 endTime   : 31345678
}
我试图找出所有冲突的数量。例如,如果有两条记录重叠,则冲突数为1。如果有三条记录,其中两条重叠,则冲突为1。如果有三条记录且所有三条记录重叠,则冲突为3,即
[(X1,X2),(X1,X3),(X2,X3)]

作为一种算法,我考虑按开始时间对数据进行排序,对每个排序的记录检查结束时间,并查找开始时间小于结束时间的记录。这将是O(n2)时间。更好的方法是使用间隔树,将每个记录插入到树中,并在发生重叠时查找计数。这将是O(nlgn)时间


我没有太多地使用mongoDB,所以我可以使用什么样的查询来实现这样的功能?

正如您正确提到的,有不同的方法,它们的执行具有不同的内在复杂性。这基本上涵盖了它们是如何完成的,以及您实际实现哪一个取决于您的数据和用例最适合于哪一个

当前范围匹配 MongoDB 3.6$lookup 最简单的方法是使用MongoDB 3.6中运算符的新语法,该语法允许将
管道
作为“自连接”到同一集合的表达式。如果当前文档的
开始时间
”或“
结束时间
”介于任何其他文档的相同值之间,则可以再次查询集合中的任何项目,当然不包括原始文档:

db.getCollection('collection').aggregate([
  { "$lookup": {
    "from": "collection",
    "let": {
      "_id": "$_id",
      "starttime": "$starttime",
      "endtime": "$endtime"
    },
    "pipeline": [
      { "$match": {
        "$expr": {
          "$and": [
            { "$ne": [ "$$_id", "$_id" },
            { "$or": [
              { "$and": [
                { "$gte": [ "$$starttime", "$starttime" ] },
                { "$lte": [ "$$starttime", "$endtime" ] }
              ]},
              { "$and": [
                { "$gte": [ "$$endtime", "$starttime" ] },
                { "$lte": [ "$$endtime", "$endtime" ] }
              ]}
            ]},
          ]
        },
        "as": "overlaps"
      }},
      { "$count": "count" },
    ]
  }},
  { "$match": { "overlaps.0": { "$exists": true }  } }
])
single对同一个集合执行“联接”,允许您通过管道阶段的
“let”
选项分别保留
“id”
“starttime”
“endtime”
值的“当前文档”值。这些变量将作为“局部变量”使用表达式后续的
“管道”
中的
$
前缀提供

在这个“子管道”中,您使用管道阶段和查询操作符,这允许您作为查询条件的一部分评估聚合框架逻辑表达式。这允许在选择与条件匹配的新文档时比较值

条件只是查找“已处理文档”,其中
“\u id”
字段不等于“当前文档”,其中
“starttime”
“endtime”
当前文档的值介于“已处理文档”的相同属性之间。这里需要注意的是,这些以及相应的and运算符是,而不是形式,因为在上下文中,由计算的返回结果必须是
布尔值
。这是聚合比较运算符实际执行的操作,也是传递值进行比较的唯一方法

因为我们只需要匹配的“计数”,所以使用管道阶段来完成这项工作。整体计算的结果将是一个有计数的“单元素”数组,或者是一个与条件不匹配的“空数组”

另一种情况是“省略”阶段,只允许返回匹配的文档。这允许轻松识别,但作为“嵌入文档中的数组”,您确实需要注意将作为整个文档返回的“重叠”数量,并且这不会导致违反BSON 16MB的限制。在大多数情况下,这应该是好的,但是对于您期望给定文档有大量重叠的情况,这可能是真实的情况。因此,这确实是更值得注意的事情

此上下文中的管道阶段将“始终”在结果中返回数组,即使为空。输出属性“合并”到现有文档中的名称将是
“重叠”
,如
“as”
属性到stage中所指定

接下来,我们可以使用正则查询表达式执行一个简单的测试,测试输出数组的
0
索引值。如果数组中确实有一些内容,因此“重叠”,则条件为true,并返回文档,根据您的选择显示计数或文档“重叠”

其他版本-查询“加入” MongoDB缺乏这种支持的另一种情况是,通过为每个检查的文档发出上述相同的查询条件来手动“加入”:

db.getCollection('collection').find().map( d => {
  var overlaps = db.getCollection('collection').find({
    "_id": { "$ne": d._id },
    "$or": [
      { "starttime": { "$gte": d.starttime, "$lte": d.endtime } },
      { "endtime": { "$gte": d.starttime, "$lte": d.endtime } }
    ]
  }).toArray();

  return ( overlaps.length !== 0 ) 
    ? Object.assign(
        d,
        {
          "overlaps": {
            "count": overlaps.length,
            "documents": overlaps
          }
        }
      )
    : null;
}).filter(e => e != null);
这基本上是相同的逻辑,只是我们实际上需要“返回数据库”,以便发出查询以匹配重叠的文档。这次是“查询运算符”,用于查找当前文档值在已处理文档值之间的位置

因为结果已经从服务器返回,所以在向输出中添加内容时没有BSON限制。您可能有内存限制,但这是另一个问题。简单地说,我们通过
.toArray()
返回数组而不是光标,这样我们就有了匹配的文档,并且可以简单地访问数组长度以获得计数。如果您实际上不需要文档,那么使用而不是
.find()
,效率要高得多,因为没有文档获取开销

然后将输出简单地与现有文档合并,其中另一个重要区别是,由于这些是“多个查询”,因此无法提供它们必须“匹配”某些内容的条件。因此,我们需要考虑计数(或数组长度)为
0
的结果,此时我们所能做的就是返回一个
null
值,稍后我们可以从结果数组中
.filter()
。其他迭代游标的方法采用相同的基本原则,即在我们不需要的地方“丢弃”结果。但是没有任何东西可以阻止查询在服务器上运行,并且这种过滤是“后处理”
{ "_id": "A", "booking": [ 10, 11, 12 ] }
{ "_id": "B", "booking": [ 12, 13, 14 ] }
{ "_id": "C", "booking": [ 7, 8 ] }
{ "_id": "D", "booking": [ 9, 10, 11 ] }
db.booking.aggregate([
  { "$unwind": "$booking" },
  { "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
  { "$match": { "docs.1": { "$exists": true } } }
])
{ "_id" : 10, "docs" : [ "A", "D" ] }
{ "_id" : 11, "docs" : [ "A", "D" ] }
{ "_id" : 12, "docs" : [ "A", "B" ] }
db.booking.aggregate([
  { "$unwind": "$booking" },
  { "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
  { "$match": { "docs.1": { "$exists": true } } },
  { "$unwind": "$docs" },
  { "$group": {
    "_id": "$docs",
    "intervals": { "$push": "$_id" }  
  }}
])
{ "_id" : "B", "intervals" : [ 12 ] }
{ "_id" : "D", "intervals" : [ 10, 11 ] }
{ "_id" : "A", "intervals" : [ 10, 11, 12 ] }