【问题标题】:MongoDB : Adding Days field to Date Type Field in DB and then comparing with the current dateMongoDB:将 Days 字段添加到 DB 中的日期类型字段,然后与当前日期进行比较
【发布时间】:2014-12-29 15:09:24
【问题描述】:

要求计算“到期日期”大于当前日期的客户记录数。

我在 MongoDB 中有一组客户。在客户文档中,有两个字段“合同日期”和“期限”(期限以月为单位)。

![在此处输入图片描述][1]

Mongo 文档中没有可以使用的直接“到期日期”字段,但可以按记录计算如下:

“合同日期”+“期限”(期限以月为单位)=到期日期。

我需要在数据库级别计算每个客户记录的到期日期,并将该日期与当前日期进行比较。如何做到这一点?

如果数据库中存在过期日期,那么我可以很容易地实现它,如下所示:

        final BasicDBList fromList = new BasicDBList();
        fromList.add("$customer.expirationDate");
        fromList.add(fromDate);

        final BasicDBList cond1 = new BasicDBList();
        cond1.add(new BasicDBObject("$gt", fromList));
        cond1.add(1);
        cond1.add(0);

        DBObject count = new BasicDBObject("$sum", new BasicDBObject("$cond", cond1)))

        groupFields.put("count", count );

        BasicDBObject group = new BasicDBObject("$group", groupFields);
        AggregationOutput output = template.getDb().getCollection("customer").aggregate(match, group); 

感谢任何帮助或建议。

【问题讨论】:

  • 我真的可以说得更多,但我只会将其限制为“我们应该回答的问题是什么?”因为我在这里没有看到一个明确的问题恕我直言。
  • 我已经用更多细节编辑了这个问题。
  • 可以使用聚合框架日期运算符和一些数学和逻辑来计算日期或与日期等效的值。但是将字段永久存储在其中您可以简单地查询那些在当前日期之后过期的记录似乎更合乎逻辑。如果你这样做,它会从聚合框架中移除很多繁重的工作,因为你可以按日期过滤。这只是光标结果上的.count(),效率更高。
  • 数据库是只读的,不允许我添加任何字段。

标签: javascript java mongodb mongodb-query aggregation-framework


【解决方案1】:

这确实是其中一种情况,您必须表明立场并做出改变,而不是试图“跳过箍”来处理其他人糟糕的设计决策。这里要扩展的“橄榄枝”是最初的设计可能没有考虑到数据将如何被使用。

您在不更改数据存储方式的情况下提出的查询需要不小的努力。我将这里的所有内容都保留为带有 JSON 表示法或其他原始 JavaScript 的“shell”形式。与其他语言一样,JSON 易于解析或转换为可用于为 Java 驱动程序构造 BSON 对象的方法。

所以,继续前进,让我们看看这里的所有案例以及如何解决,以及限制以及最终在此处进行更改的好处。

考虑我们“即将到期”的集合中的以下示例:

{ "contractDate" : ISODate("2014-04-23T00:00:00Z"), "term" : 10 }
{ "contractDate" : ISODate("2014-04-23T00:00:00Z"), "term" : 7 }
{ "contractDate" : ISODate("2014-11-30T00:00:00Z"), "term" : 1 }

MongoDB 有一个 $where 运算符,它将在服务器上运行任意 JavaScript 代码(作为 JavaDriver 的字符串提供)。定义的函数必须返回true/false,以确定是否满足查询条件。基本上将“contractDate”+“term”评估为当前日期,或者由允许您将变量“范围”到评估的 JavaScript 的变体提供的日期:

db.expiring.count({
  "$where": function () {

    var now = new Date(),
        today = new Date(
          now.valueOf() -
          ( now.valueOf() % ( 1000 * 60 * 60 * 24 ) )
        );

    var adjustedMonth =
      this.contractDate.getMonth() + 1 + this.term;

    var year = ( adjustedMonth > 12 ) ?
      this.contractDate.getFullYear() + 1
      : this.contractDate.getFullYear();

    var month = ( adjustedMonth > 12 ) ?
      adjustedMonth - 12 : adjustedMonth;

    var day = this.contractDate.getDate();

    var expiring = new Date( year + "-" + month + "-" + day );
    return expiring > today;
  }
})

这太可怕了,因为您既要强行针对集合中的每个文档评估条件,又要针对集合中的每个项目强制服务器端评估和执行 JavaScript 代码。由于它计算评估,因此您不能使用索引来改进任何东西。

您还可以通过聚合框架计算日期并进行比较。为了一点可读性(也让我自己动手),这里的示例分两个阶段给出,但可以在单个 $group 阶段完成:

db.expiring.aggregate([
  { "$project": {
    "contractDate": 1,
    "term": 1,
    "expires": {
      "year": {
        "$cond": [
          { "$gt": [ 
            { "$add": [{ "$month": "$contractDate" }, "$term" ] },
            12
          ]},
          { "$add": [{ "$year": "$contractDate" }, 1 ] },
          { "$year": "$contractDate" }
        ]
      },
      "month": {
        "$cond": [
          { "$gt": [
            { "$add": [{ "$month": "$contractDate" }, "$term" ] },
            12
          ]},
          { "$subtract": [
            { "$add": [{ "$month": "$contractDate" }, "$term" ] },
            12
          ]},
          { "$add": [{ "$month": "$contractDate" }, "$term" ] }
        ]
      },
      "day": { "$dayOfMonth": "$contractDate" }
    }
  }},
  { "$group": {
    "_id": null,
    "count": {
      "$sum": {
        "$cond": [
          { "$or": [
             { "$gt": [ "$expires.year", thisYear ] },
             { "$and": [
               { "$eq": [ "$expires.year", thisYear ] },
               { "$gt": [ "$expires.month", thisMonth ] },
             ]},
             { "$and": [
               { "$eq": [ "$expires.year", thisYear ] },
               { "$eq": [ "$expires.month", thisMonth ] },
               { "$gt": [ "$expires.day", thisDay ] }     
             ]}
          ]},
          1,
          0
        ]
      }
    }
  }}
])

当然,在构建表示当前日期时会输入外部变量。在这里,它们被分解为thisYearthisMonththisDay 以匹配显示的模式。您还可以使用类似于 JavaScript 代码的“日期数学”方法。

再一次,这太可怕了。即使在单个管道阶段,这仍然需要贯穿整个集合。本地操作符会加快速度,但不会快很多,当然你仍然不能使用索引。

这就是您应该更改数据存储方式的原因。考虑一下何时文档看起来像这样:

{ 
    "contractDate" : ISODate("2014-04-23T00:00:00Z"), 
    "term" : 10,
    "expiry": ISODate("2015-02-23T00:00:00Z") 
}
{ 
    "contractDate" : ISODate("2014-04-23T00:00:00Z"), 
    "term" : 7,
    "expiry" : ISODate("2014-11-23T00:00:00Z"),         
}
{ 
    "contractDate" : ISODate("2014-11-30T00:00:00Z"),
    "term" : 1,
    "expiry": ISODate("2014-12-30T00:00:00Z")
}

现在还要考虑新的expiry 字段也被索引了,现在获取计数的非常有效的方法是非常基本的:

db.expiring.count({ "expiry": { "$gt": new Date("2014-12-30") } })

就是这样!只有那些大于指定索引范围的项目被触及,您只需计算仍处于活动状态的项目的数量,而无需计算或评估任何内容。

因此,我认为需要更改维护此数据的代码,以在文档中保留此附加字段,并在任何更改时保留“contractDate”和“term”字段中的两个相关字段。

操作很简单,应该不难,并且应该谈论维护此代码的“非常小的”更改,以及对现有数据的“一次性”更新以使其如此。所以平衡要么是“小变化”,要么是实施“大混乱”,只是为了报告不存在的东西。

我强烈建议您将其展示给能够做出更改决定的人。这将节省您和其他所有人的时间。没有人想要缓慢而长时间运行的查询。这也是要花钱的。

【讨论】:

  • 感谢尼尔的详细解释。
【解决方案2】:

您无法以您想要的方式直接搜索 MongoDB。至于解决方案,首先是一个问题:您是否要在其他地方对Contract DateTERM 运行查询?如果没有,您可以删除其中一个字段并存储Expiration Date,因为 A + B = C 可以转换为 B = C - A 等等。如果您需要对所有 3 个字段运行查询,则需要添加此重复数据或过滤 Java 代码中的所有记录

【讨论】:

  • “ControlAtlDel”?我认为你应该硬重启。这个问题的形式很差,因此要求澄清。但是使用“范围”查询并没有错。而且(不建议)您“可以”实际调用 JavaScript 以在服务器上工作并评估诸如当前“日期”对象之类的东西以进行比较。所以基本上是错误的,但正如我所说,在刺入完全错误的评论之前,可以“询问”他们真正想要做什么。这也不是真正解决任何问题的答案。
  • 合同日期和期限是 MongoDB 文档中唯一存在的字段。数据库是只读的。 A+B 生成 C,然后需要将 C 与当前日期进行比较。
猜你喜欢
  • 1970-01-01
  • 2018-04-04
  • 2015-09-01
  • 1970-01-01
  • 2016-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多