在一个聚合

时间:2018-05-07 22:17:40

标签: mongodb mongoose mongodb-query aggregation-framework

我有一个如下所示的聚合:

userSchema.statics.getCounts = function (req, type) {
  return this.aggregate([
    { $match: { organization: req.user.organization._id } },
    {
      $lookup: {
        from: 'tickets', localField: `${type}Tickets`, foreignField: '_id', as: `${type}_tickets`,
      },
    },
    { $unwind: `$${type}_tickets` },
    { $match: { [`${type}_tickets.createdAt`]: { $gte: new Date(moment().subtract(4, 'd').startOf('day').utc()), $lt: new Date(moment().endOf('day').utc()) } } },
    {
      $group: {
        _id: {
          groupDate: {
            $dateFromParts: {
              year: { $year: `$${type}_tickets.createdAt` },
              month: { $month: `$${type}_tickets.createdAt` },
              day: { $dayOfMonth: `$${type}_tickets.createdAt` },
            },
          },
          userId: `$${type}_tickets.assignee_id`,
        },
        ticketCount: {
          $sum: 1,
        },
      },
    },
    {
      $sort: { '_id.groupDate': -1 },
    },
    { $group: { _id: '$_id.userId', data: { $push: { groupDate: '$_id.groupDate', ticketCount: '$ticketCount' } } } },
  ]);
};

输出如下数据:

[ 
  {
    _id: 5aeb6b71709f43359e0888bb,
    data: [ 
      { "groupDate": 2018-05-07T00:00:000Z", ticketCount: 4 }
  }
]

理想情况下,我会有这样的数据:

[ 
  {
    _id: 5aeb6b71709f43359e0888bb,
    data: [ 
      { "groupDate": 2018-05-07T00:00:000Z", assignedCount: 4, resolvedCount: 8 }
  }
]

不同之处在于,用户的对象将同时输出已分配票证的总数和每个日期的已解决票证总数。

我的userSchema是这样的:

const userSchema = new Schema({
  firstName: String,
  lastName: String,
  assignedTickets: [
    {
      type: mongoose.Schema.ObjectId,
      ref: 'Ticket',
      index: true,
    },
  ],
  resolvedTickets: [
    {
      type: mongoose.Schema.ObjectId,
      ref: 'Ticket',
      index: true,
    },
  ],
}, {
  timestamps: true,
});

示例用户文档是这样的:

{
    "_id": "5aeb6b71709f43359e0888bb", 
    "assignedTickets": ["5aeb6ba7709f43359e0888bd", "5aeb6bf3709f43359e0888c2", "5aec7e0adcdd76b57af9e889"], 
    "resolvedTickets": ["5aeb6bc2709f43359e0888be", "5aeb6bc2709f43359e0888bf"], 
    "firstName": "Name", 
    "lastName": "Surname", 
}

示例故障单文档是这样的:

{
    "_id": "5aeb6ba7709f43359e0888bd", 
    "ticket_id": 120292, 
    "type": "assigned", 
    "status": "Pending", 
    "assignee_email": "email@gmail.com", 
    "assignee_id": "5aeb6b71709f43359e0888bb", 
    "createdAt": "2018-05-02T20:05:59.147Z", 
    "updatedAt": "2018-05-03T20:05:59.147Z", 
}

我已经尝试添加多个查找和组阶段,但我不断获得一个空数组。如果我只进行一次查找和一组,我会获得搜索字段的正确计数,但我希望在一个查询中同时包含两个字段。是否可以在两次查找中使用查询组?

2 个答案:

答案 0 :(得分:2)

这样的事情怎么样?

gf = 0

答案 1 :(得分:2)

简而言之,您似乎已经接受了在mongoose中设置模型的条款,并且已经过多了参考文献。实际上,您确实不应该将数组保留在"User"文档中。这实际上是一种反模式"这只是mongoose最初用作保持"参考"对于那些不了解如何将参考文献保存在" child"到了父母"代替。

您实际拥有每个"Ticket"中的数据,$lookup的自然形式是使用"foreignField"来引用本地集合中的详细信息。在这种情况下,故障单上的"assignee_id"就足以查看匹配回"_id"的{​​{1}}。虽然您没有说明,但您的"User"应该是数据是否实际被指定为"就像在"待定"州或"已解决"当它不是。

为了简单起见,我们将考虑国家"已解决"如果它不是"等待"在价值方面,但是从实例的逻辑中扩展实际需求并不是问题所在。

基本上,我们通过实际使用自然"外键"来解决 $lookup操作问题。而不是保持单独的数组。

MongoDB 3.6及更高版本

理想情况下,您可以使用MongoDB 3.6中的子流水线处理功能:

"status"

从MongoDB 3.0及以上

或者,如果您缺少这些功能,我们会在从服务器返回结果后使用不同的管道流程和一些数据转换:

// Better date calculations
const oneDay = (1000 * 60 * 60 * 24);
var now = Date.now(),
    end = new Date((now - (now % oneDay)) + oneDay),
    start = new Date(end.valueOf() - (4 * oneDay));

User.aggregate([
  { "$match": { "organization": req.user.organization._id } },
  { "$lookup": {
    "from": Ticket.collection.name,
    "let": { "id": "$_id" },
    "pipeline": [
      { "$match": {
        "createdAt": { "$gte": start, "$lt": end },
        "$expr": {
          "$eq": [ "$$id", "$assignee_id" ]
        }
      }},
      { "$group": {
        "_id": {
          "status": "$status",
          "date": {
            "$dateFromParts": {
              "year": { "$year": "$createdAt" },
              "month": { "$month": "$createdAt" },
              "day": { "$dayOfMonth": "$createdAt" }
            }
          }
        },
        "count": { "$sum": 1 }
      }},
      { "$group": {
        "_id": "$_id.date",
        "data": {
          "$push": {
            "k": {
              "$cond": [
                { "$eq": ["$_id.status", "Pending"] },
                "assignedCount",
                "resolvedCount"
              ]
            },
            "v": "$count"
          }
        }
      }},
      { "$sort": { "_id": -1 } },
      { "$replaceRoot": {
        "newRoot": {
          "$mergeObjects": [
            { "groupDate": "$_id", "assignedCount": 0, "resolvedCount": 0 },
            { "$arrayToObject": "$data" }
          ]
        }
      }}
    ],
    "as": "data"
  }},
  { "$project": { "data": 1 } }
])

这真的表明,即使现代版本中的花哨功能,你真的不需要它们,因为几乎总有办法解决这个问题。即使是JavaScript部分也只是在当前"对象传播之前稍微延长了版本。语法可用。

这真的是你需要进入的方向。你当然不想要的是使用"多个" $lookup阶段甚至在可能是大型数组的情况下应用$filter条件。此外,这两种形式都尽力过滤"过滤"项目数量"加入"来自国外的收藏品,以免造成违反BSON限制。

特别是"前3.6"版本实际上有一个技巧,User.aggregate([ { "$match": { "organization": req.user.organization._id } }, { "$lookup": { "from": Ticket.collection.name, "localField": "_id", "foreignField": "assignee_id", "as": "data" }}, { "$unwind": "$data" }, { "$match": { "data.createdAt": { "$gte": start, "$lt": end } }}, { "$group": { "_id": { "userId": "$_id", "date": { "$add": [ { "$subtract": [ { "$subtract": [ "$data.createdAt", new Date(0) ] }, { "$mod": [ { "$subtract": [ "$data.createdAt", new Date(0) ] }, oneDay ]} ]}, new Date(0) ] }, "status": "$data.status" }, "count": { "$sum": 1 } }}, { "$group": { "_id": { "userId": "$_id.userId", "date": "$_id.date" }, "data": { "$push": { "k": { "$cond": [ { "$eq": [ "$_id.status", "Pending" ] }, "assignedCount", "resolvedCount" ] }, "v": "$count" } } }}, { "$sort": { "_id.userId": 1, "_id.date": -1 } }, { "$group": { "_id": "$_id.userId", "data": { "$push": { "groupDate": "$_id.date", "data": "$data" } } }} ]) .then( results => results.map( ({ data, ...d }) => ({ ...d, data: data.map(di => ({ groupDate: di.groupDate, assignedCount: 0, resolvedCount: 0, ...di.data.reduce((acc,curr) => ({ ...acc, [curr.k]: curr.v }),{}) }) ) }) ) ) 连续发生,你可以在解释输出中看到。所有阶段实际上都组合成一个"一个"那里的阶段只返回与外国集合中$match的条件匹配的项目。保持事物"解开"直到我们进一步减少BSON限制问题,以及MongoDB 3.6的新表格,其中"子管道"在返回任何结果之前完成所有文档缩减和分组。

您的一个文档示例将返回如下:

$lookup + $unwind + $match

我将日期选择扩展为包含该日期后,当然也可以从原始表单中改进和更正日期选择。

所以似乎有意义的是你的关系实际上是以这种方式定义的,但只是你记录了他们的关系"两次"。你不需要,即使那不是定义,那么你实际上应该在"孩子上记录#34;而不是父母中的数组。我们可以兼顾和合并父数组,但这对于实际正确建立数据关系并正确使用它们会产生反作用。