【问题标题】:How can I ensure my aggregation filters out subdocuments that are past expiry date in mongo?如何确保我的聚合过滤掉 mongo 中过期的子文档?
【发布时间】:2020-12-20 05:48:11
【问题描述】:

我想计算当前处于活动状态的 resetPassword(用户架构中的子文档)代码的数量。要使代码处于活动状态,其到期日期必须大于当前日期。

这是我的用户架构。如果有人要求重置密码,我们会将一个新的{ code: X, expiresAt, createdAt } 对象推送到数组中。

id: { type: String, unique: true },
resetPassword: [
  {
    code: String,
    expiresAt: Date,
    createdAt: Date,
  },
],

我在尝试对活动重置代码的总数进行 $sum 时遇到问题。这是我正在运行的返回空数组的查询...请注意,如果我要删除 resetPassword.expiresAt: { $gt: nowDateInMilliseconds() } 匹配部分,它将返回所有代码。我尝试将此匹配语句移出初始 $match 阶段,然后在 expiresAt 上进行展开和匹配,但这也不起作用。

[
  {
    $match: {
      "id": userId,
      'resetPassword.expiresAt': {
        $gt: nowDateInMillisec(),
      },
    },
  },
  {
    $group: {
      _id: '$id',
      totalValidResetCodes: {
        $sum: {
          $size: '$resetPassword',
        },
      },
    },
  },
]

这将返回一个空数组,即使我已将到期日期设置为将来的某个日期。

我还尝试了以下相同的结果(注意我是如何向管道添加 $unwind 和另一个 $match 的)

[
  {
    $match: {
      "id": userId,
    },
  },
  {
    $unwind: '$resetPassword',
  },
  {
    $match: {
      'resetPassword.expiresAt': {
        $gt: nowDateInMillisec(),
      },
    }
  },
  {
    $group: {
      _id: '$id',
      totalValidResetCodes: {
        $sum: {
          $size: '$resetPassword',
        },
      },
    },
  },
]
  • nowDateInMillisec() - 这只是从纪元返回当前日期(以毫秒为单位)。

我做错了什么?

【问题讨论】:

  • 可能是类型不匹配。如果您使用日期类型而不是毫秒,它的行为会有所不同吗?
  • @Joe You 美女!就是这样......我认为使用milliseconds 是可以的,Mongo 会在那里处理转换!如果您想添加您的答案,我会接受。
  • @Joe 当我在我的猫鼬模式中设置了Date 类型时,我是否正确地将日期插入为milliseconds?我应该使用Integer 类型而不是使用Date 类型吗?

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

您可以在$project 中尝试$reduce,而不是您的所有过程,您需要从这个nowDateInMillisec() 返回ISOdate,

db.collection.aggregate([
  { $match: { id: 1 } },
  {
    $project: {
      totalValidResetCodes: {
        $reduce: {
          input: "$resetPassword",
          initialValue: 0,
          in: {
            $add: [
              "$$value",
              {
                $cond: [
                  { $gt: ["$$this.expiresAt", nowDateInMillisec()] },
                  // If you really want to pass timestamp then try below line
                  // { $gt: ["$$this.expiresAt", { $toDate: nowDateInMillisec() }] },
                  1,
                  0
                ]
              }
            ]
          }
        }
      }
    }
  }
])

Playground

【讨论】:

    【解决方案2】:

    以下是我的研究,以 mongodb 格式存储的日期格式也应该适用于毫秒。以下测试最多持续一分钟。

    > db.users13.find().pretty();
    {
            "_id" : ObjectId("5f4e03768379a4e3f957641d"),
            "id" : "johnc",
            "resetPassword" : [
                    {
                            "code" : "abc",
                            "expiresAt" : ISODate("2020-09-02T09:11:18.394Z"),
                            "createdAt" : ISODate("2020-09-01T08:11:18.394Z")
                    },
                    {
                            "code" : "mno",
                            "expiresAt" : ISODate("2020-08-25T09:26:18.394Z"),
                            "createdAt" : ISODate("2020-08-25T08:11:18.394Z")
                    }
            ]
    }
    {
            "_id" : ObjectId("5f4e06938379a4e3f957641f"),
            "id" : "katey",
            "resetPassword" : [
                    {
                            "code" : "j2c",
                            "expiresAt" : ISODate("2020-09-02T08:48:18.394Z"),
                            "createdAt" : ISODate("2020-09-01T08:11:18.394Z")
                    },
                    {
                            "code" : "rml",
                            "expiresAt" : ISODate("2020-09-01T08:26:18.394Z"),
                            "createdAt" : ISODate("2020-09-01T08:11:18.394Z")
                    }
            ]
    }
    > db.users13.aggregate([ 
       {$unwind:"$resetPassword"}, 
       {$match:{"resetPassword.expiresAt":{$gt:ISODate()}}} 
     ]).pretty();
    {
            "_id" : ObjectId("5f4e03768379a4e3f957641d"),
            "id" : "johnc",
            "resetPassword" : {
                    "code" : "abc",
                    "expiresAt" : ISODate("2020-09-02T09:11:18.394Z"),
                    "createdAt" : ISODate("2020-09-01T08:11:18.394Z")
            }
    }
    {
            "_id" : ObjectId("5f4e06938379a4e3f957641f"),
            "id" : "katey",
            "resetPassword" : {
                    "code" : "j2c",
                    "expiresAt" : ISODate("2020-09-02T08:48:18.394Z"),
                    "createdAt" : ISODate("2020-09-01T08:11:18.394Z")
            }
    }
    > ISODate()
    ISODate("2020-09-01T08:31:37.059Z")
    >
    

    【讨论】:

      【解决方案3】:

      @Joe 在 cmets 中回答了我的问题。他暗示在匹配过滤器中使用自纪元以来的毫秒数是行不通的,因为我在 Mongoose 架构中使用了 Date 类型。

      所以不是这样做:$gt: nowDateInMillisec(), 我只是使用了Date 类型,如下所示:$gt: new Date(),

      【讨论】:

        猜你喜欢
        • 2016-06-26
        • 2016-08-29
        • 1970-01-01
        • 2021-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-08
        相关资源
        最近更新 更多