聚合时间安排

时间:2017-09-19 00:28:53

标签: node.js mongodb mongoose aggregation-framework

我正在使用聚合来查询我的架构的日期范围内的计数,我的问题是我没有从服务器得到任何响应(每次都超时),其他mongoose查询工作正常(查找,保存等)和当我调用聚合时,它取决于管道(当我只使用匹配时,我得到一个响应,当我添加放松,我没有得到任何)。


连接代码:

<div class="dots-wrap">
  <form action="#">
    <div>
      <h2 class="strikethrough">
        <p>
          <a href="#tab-t1">
            <input type="radio" id="test1" name="radio-group" checked>

            <label for="test1"></label>
          </a>
        </p>
        <p>
          <a href="#tab-t2">
            <input type="radio" id="test2" name="radio-group">
            <label for="test2"></label>
          </a>
        </p>
        <p>
          <a href="#tab-t3">
            <input type="radio" id="test3" name="radio-group">
            <label for="test3"></label>
          </a>
        </p>
        <p>
          <a href="#tab-t4">
            <input type="radio" id="test3" name="radio-group">
            <label for="test4"></label>
          </a>
        </p>
      </h2>
    </div>
  </form>
  <div>
    <div>
      <label for="test1">Marketing & Lead Generation</label>
    </div>
    <div>
      <label for="test2">Underwriting</label>
    </div>
    <div>
      <label for="test3">Customer Management</label>
    </div>
    <div>
      <label for="test4">Fraud, Collections & Recoveries</label>
    </div>
  </div>
</div>

架构:

var promise = mongoose.connect('mongodb://<username>:<password>@<db>.mlab.com:<port>/<db-name>', {
  useMongoClient: true,
  replset: {
    ha: true, // Make sure the high availability checks are on
    haInterval: 5000 // Run every 5 seconds
  }
});

promise.then(function(db){
  console.log('DB Connected');
}).catch(function(e){
  console.log('DB Not Connected');
  console.errors(e.message);
  process.exit(1);
});

汇总代码:

var ProspectSchema = new Schema({
  contact_name: {
    type: String,
    required: true
  },
  company_name: {
    type: String,
    required: true
  },
  contact_info: {
    type: Array,
    required: true
  },
  description:{
    type: String,
    required: true
  },
  product:{
    type: Schema.Types.ObjectId, ref: 'Product'
  },
  progression:{
    type: String
  },
  creator:{
    type: String
  },
  sales: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  },
  technical_sales: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  },
  actions: [{
    type: {type: String},
    description: {type: String},
    date: {type: Date}
  }],
  sales_connect_id: {
    type: String
  },
  date_created: {
    type: Date,
    default: Date.now
  }
});

调用聚合:

exports.getActionsIn = function(start_date, end_date) {
  var start = new Date(start_date);
  var end = new Date(end_date);

  return Prospect.aggregate([
    {
      $match: {
        // "actions": {
        //   $elemMatch: {
        //     "type": {
        //       "$exists": true
        //     }
        //   }
        // }
        "actions.date": {
          $gte: start,
          $lte: end
        }
      }
    }
    ,{
      $project: {
        _id: 0,
        actions: 1
      }
    }
    ,{
      $unwind:  "actions"
    }
    ,{
      $group: {
        _id: "actions.date",
        count: {
          $sum: 1
        }
      }
    }
    // ,{
    //   $project: {
    //     _id: 0,
    //     date: {
    //       $dateToString: {
    //         format: "%d/%m/%Y",
    //         date: "actions.date"
    //       }
    //     }
    //     // ,
    //     // count : "$count"
    //   }
    // }
  ]).exec();
}

我的主要问题是我根本没有得到任何回复,我可以处理错误信息,问题是我没有得到任何回复,所以我不知道有什么问题。

猫鼬版本:4.11.8

P.S。我尝试了聚合管道的多种变体,所以这不是我的第一次尝试,我有一个聚合工作在主要的前景模式而不是动作子文档

1 个答案:

答案 0 :(得分:1)

这里有几个问题,主要是缺少概念。懒惰的读者可以跳到底部以获取完整的管道示例,但这里的主体是解释为什么事情按原样完成。

  1. 您正尝试在日期范围内进行选择。检查任何长时间运行操作的第一件事是你有一个有效的索引。你可能有一个,或者你可能没有。但你应该发出:(来自shell)

    db.prospects.createIndex({ "actions.date": 1 })
    

    只是为了确定。您可能真的应该将其添加到架构定义中,以便您知道应该部署它。因此,添加到您定义的架构:

    ProspectSchema.index({ "actions.date": 1 })
    
  2. 查询&#34;范围&#34;在数组的元素上,你需要了解那些是&#34;多个条件&#34;您期望匹配元素&#34;在&#34;之间。虽然你通常可以逃避查询一个单一的财产&#34;使用&#34; Dot Notation&#34;的数组,你遗漏了[$gte][1]$lte的应用就像明确地用$and多次指定属性一样。

    每当你有这样的&#34;多个条件&#34; 始终意味着使用$elemMatch。没有它,您只需测试数组中的每个值,看它是否大于或小于(有些可能更大,有些可能更小)。 $elemMatch运算符确保&#34;两者都是&#34;应用于相同的&#34;元素&#34;,而不仅仅是所有数组值为&#34;点符号&#34;暴露他们:

    { "$match": {
      "actions": {
        "$elemMatch": { "date": { "$gte": start, "$lte: end } }
      }
    }}
    

    现在只能匹配&#34;数组元素&#34;落在指定日期之间。没有它,您正在选择和处理与选择无关的更多数据。

  3. 数组过滤:标记为粗体,因为它的突出性无法忽略。任何初始$match都可以像任何&#34;查询&#34;因为它的工作&#34;是选择文件&#34;对表达式有效。但是不会对返回的文档中的数组内容产生任何影响。

    每当你有这样的文件选择条件时,你几乎总是打算过滤&#34;过滤&#34;来自阵列本身的这样的内容。这是一个单独的过程,实际上应该在使用内容的任何其他操作之前执行。特别是[$unwind][4]

    所以你真的应该在$filter$addFields中添加一个$project,以适合你的意图&#34;立即&#34;在选择任何文件之后:

    { "$project": {
      "_id": 0,
      "actions": {
        "$filter": {
          "input": "$actions",
          "as": "a",
          "in": {
            "$and": [
              { "$gte": [ "$$a.date", start ] },
              { "$lte": [ "$$a.date", end ] }
            ]
          }
        }
      }
    }}
    

    现在您已经知道的数组内容&#34;必须&#34;由于初始查询条件,至少包含一个有效项目,是&#34;减少&#34;仅限于那些与您想要的日期范围实际匹配的条目。这消除了以后处理的大量开销。

    注意不同的&#34;逻辑变体&#34;在$gte条件下使用的$lte$filter。这些求值会为需要它们的表达式返回一个布尔值。

  4. 分组它可能只是尝试获得结果,但您拥有的代码并没有真正对相关日期做任何事情。由于典型的日期值应该以毫秒精度提供,因此您通常希望减少它们。

    评论代码建议在$dateToString内使用$project。强烈建议您不要这样做。如果您打算进行此类缩减,请将该表达式直接提供给$group内的分组键:

    { "$group": {
      "_id": {
        "$dateToString": {
          "format": "%Y-%m-%d",
          "date": "$actions.date"
        }
      },
      "count": { "$sum": 1 }
    }}
    

    我个人不喜欢回复&#34;字符串&#34;当一个自然Date对象已经为我正确序列化时。所以我喜欢使用&#34;数学&#34;接近&#34; round&#34;日期代替:

    { "$group": {
      "_id": {
        "$add": [
          { "$subtract": [
            { "$subtract": [ "$actions.date", new Date(0) ] },
            { "$mod": [
              { "$subtract": [ "$actions.date", new Date(0) ] },
              1000 * 60 * 60 * 24
            ]}
          ],
          new Date(0)
        ]
      },
      "count": { "$sum": 1 }
    }}
    

    返回有效的Date对象&#34;舍入&#34;到了今天。里程可能因首选方法而异,但它是我喜欢的。并且传输的字节数最少。

    Date(0)的使用代表&#34;纪元日期&#34;。因此当你$subtract一个BSON日期从另一个BSON日期开始时,你最终将两者之间的毫秒差异作为整数。当$add为BSON日期的整数值时,您将获得一个新的BSON日期,表示两者之间的毫秒值之和。这是转换为数字,舍入到最近的一天开始,然后将数字转换回日期值的基础。

    通过直接在$group而不是$project内创建该语句,您基本上可以保存实际被解释为&#34;遍历所有数据并返回此计算值,然后去做...&#34; 。与在一堆物体上工作大致相同,先用笔标记它们,然后将它们作为一个单独的步骤进行计数。

    作为单个管道阶段,它会在计算要累积的值的同时进行累积,从而节省大量资源。如果您认为它与提供的类比非常相似,那么它就非常有意义。

  5. 作为完整的管道示例,您可以将上述内容放在一起:

    Prospect.aggregate([
        { "$match": {
          "actions": {
            "$elemMatch": { "date": { "$gte": start, "$lte: end } }
          }
        }},
        { "$project": {
          "_id": 0,
          "actions": {
            "$filter": {
              "input": "$actions",
              "as": "a",
              "in": {
                "$and": [
                  { "$gte": [ "$$a.date", start ] },
                  { "$lte": [ "$$a.date", end ] }
                ]
              }
            }
          }
        }},
        { "$unwind": "$actions" },
        { "$group": {
          "_id": {
            "$dateToString": {
              "format": "%Y-%m-%d",
              "date": "$actions.date"
            }
          },
          "count": { "$sum": 1 }
        }}
    ])
    

    老实说,如果在确定索引到位后,并且在该管道之后仍然存在超时问题,那么请缩短日期选择,直到获得合理的响应时间。

    如果它仍然花费太长时间(或日期减少不合理),那么您的硬件根本无法完成任务。如果你真的有很多数据,那么你必须对期望合理。所以扩展或扩展,但这些事情超出了任何问题的范围。

    目前,这些改进应该对目前为止所展示的任何尝试产生重大影响。主要是由于一些基本概念被遗漏。