要求是计算“过期日期”大于当前日期的客户记录的数量。
我在 MongoDB 中有一组客户。在客户文档中,有两个字段 'Contract date' 和 'TERM'(Term in months)。
![在此处输入图片描述][1]
在 Mongo 文档中没有直接的'Expiration Date'字段可以使用但是它可以按每条记录计算如下:
“契约(Contract)日期”+“期限”(以月为单位的期限)=到期日。
我需要在数据库级别计算每个客户记录的到期日期,并将该日期与当前日期进行比较。如何实现?
如果数据库中存在 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);
如有任何帮助或建议,我们将不胜感激。
最佳答案
这确实是这样一种情况,在这种情况下,您必须站出来做出改变,而不是试图“跳过障碍”来处理其他人糟糕的设计决策。这里抛出的“橄榄枝”是初始设计可能没有充分考虑数据的使用方式。
您在不更改数据存储方式的情况下提出的查询需要付出不小的努力。我将这里的所有内容都保留为带有 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
]
}
}
}}
])
当然在构建表示当前日期时输入外部变量。此处它们被分解为 thisYear、thisMonth 和 thisDay 以匹配显示的模式。您还可以使用类似于 JavaScript 代码的“日期数学”方法。
再一次,这太可怕了。即使在单个流水线阶段,这仍然需要贯穿整个集合。 native 运算符会稍微加快速度,但不会快那么多,当然您仍然不能使用索引。
这就是您应该更改数据存储方式的原因。考虑一下文档何时变成这样:
{
"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”字段。
操作很简单,应该不难,应该是在维护这个代码中进行“非常小”的更改,加上对现有数据的“一次性”更新以实现这一点。因此,平衡要么是“小改动”,要么是实现“大困惑”,只是为了报告不存在的东西。
我强烈建议您将此展示给可以做出更改决定的人。这将节省您和其他人的时间。没有人想要缓慢而长时间运行的查询。这也需要花钱。
关于javascript - MongoDB : Adding Days field to Date Type Field in DB and then comparing with the current date,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27691313/