MongoDB:在数据库中将日期字段添加到日期类型字段,然后与当前日期进行比较

时间:2014-12-29 15:09:25

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

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

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

![在此处输入图片说明] [1]

Mongo文档中没有直接的“过期日期”字段可以使用但是可以按照每条记录计算:

'合约日期'+'期限'(以月为单位)=到期日。

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

如果DB中存在expirationDate,那么我可以轻松实现,如下所示:

        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); 

感谢任何帮助或建议。

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

这实际上是你必须站出来改变一些事情而不是试图“跳过篮球”来处理别人糟糕的设计决定的情况之一。这里延伸的“橄榄枝”是初始设计可能没有考虑到如何使用数据。

您提出的查询在不改变数据存储方式的情况下需要花费很少的精力。我将所有内容都以“shell”形式保留在JSON表示法或其他原始JavaScript中。与其他语言一样,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”字段。

操作很简单,不应该很难,并且应该在维护这个问题的代码中谈论“非常小的”变化,加上对现有数据的“一次性”更新来实现这一目标。因此,平衡要么是“小变化”,要么是为了报告不存在的东西而实施“重大混乱”。

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