终于在this answer之后得到了答案。它使用the Aggregation Pipeline。
首先,我决定使用Date/ISODate 类型来存储生日,而不是单独的年/月/日字段。所以我的test 集合会有这些条目:
{
"birthday" : ISODate("1985-04-16T10:00:00.000Z"),
"name" : "J"
}
{
"birthday" : ISODate("1985-09-16T11:00:00.000Z"),
"name" : "E"
}
{
"birthday" : ISODate("1950-11-11T11:00:00.000Z"),
"name" : "M"
}
{
"birthday" : ISODate("1947-05-06T10:00:00.000Z"),
"name" : "D"
}
然后,我构建了一些用于聚合管道的结构:
- 在
pDayOfYear 中获取生日和今天的连续年份,使用$dayOfYear operator。
- 如果
birthday 是闰年,则在 pLeapYear 字段中减去 1 到 dayofYear 并计算每个生日与今天之间的差异。
- 在
pPast 中,将 365 添加到过去的生日以拥有 positiveDiff 字段。
- 在
pSort 中按positiveDiff 字段降序排列结果,这样我们就可以得到下一个生日的列表
- 在
pFirst 中只得到第一个结果。
查询看起来像:
pDayOfYear = {
"$project": {
"birthday": 1,
"todayDayOfYear": {"$dayOfYear": new ISODate()},
"leap": {"$or": [
{"$eq": [0, {"$mod": [{"$year": "$birthday"}, 400]}]},
{"$and": [
{"$eq": [0, {"$mod": [{"$year": "$birthday"}, 4]}]},
{"$ne": [0, {"$mod": [{"$year": "$birthday"}, 100]}]}
]}
]},
"dayOfYear": {"$dayOfYear": "$birthday"}
}
}
pLeapYear = {
"$project": {
"birthday": 1,
"todayDayOfYear": 1,
"dayOfYear": {
"$subtract": [
"$dayOfYear",
{
"$cond": [
{"$and":
["$leap",
{"$gt": ["$dayOfYear", 59]}
]},
1,
0]
}
]},
"diff": { "$subtract": [ "$dayOfYear", "$todayDayOfYear"] }
}
}
pPast = {
"$project": {
"diff": 1,
"birthday": 1,
"positiveDiff": {
"$cond": {
"if": { "$lt": ["$diff", 0 ]},
"then": { "$add": ["$diff", 365] },
"else": "$diff"
},
}
}
}
pSort = {
"$sort": {
"positiveDiff": 1
}
}
pFirst = {
"$group": {
"_id": "first_birthday",
"first": {
"$first": "$$ROOT"
}
}
}
db.getCollection('tests').aggregate(pDayOfYear, pLeapYear, pPast, pSort, pFirst);
所以我会得到下一个生日 ID,我可以按 ID 查询该字段。可以在任何日期更改todayDayOfYearfield 的特定日期的下一个生日。
我愿意接受任何更改,尤其是可读性和效率改进。