【问题标题】:Mongoose Filter based on Dynamic Date Key with valueMongoose 过滤器基于带值的动态日期键
【发布时间】:2019-09-02 20:19:07
【问题描述】:

我创建了一个员工考勤应用程序,其中考勤记录并存储在数据库中。我试图获取值为"Present" 的所有日期字段的计数。数据存储在数据库中,如下所示:

"attendances": { <YYYY-MM-DD>: "value" } pair 
// The values being "Absent" or "Present" whatever the case may be. 

问题是,每当我尝试用"attendances": {"2019-08-28": "Present"} 计算所有条目时,我都会得到0 的值。

谁能帮我找出做错了什么?

//架构

const Schema = mongoose.Schema;
const employeeSchema = new Schema({
  name: String,
  department: String,
  origin: String,
  employDate: String,
  attendances: Object
});
module.exports= Employee = mongoose.model('Employee', employeeSchema);

route.js

router.get('/',(req,res)=>{
  Employee.collection.countDocuments({"attendances.date":"present"},(err,data)=>{
    if(err){
      res.status(500)
      res.send(err)
    }else{
      res.status(200)
      res.json(data)
    }
  })

})

//存储在MongoDB中的数据

  {
        "_id": "5d6565236574b1162c349d8f",
        "name": "Benjamin Hall",
        "department": "IT",
        "origin": "Texas",
        "employDate": "2019-08-27",
        "__v": 0,
        "attendances": {
            "2019-08-28": "Sick"
        }
    },
    {
        "_id": "5d6367ee5b78d30c74be90e6",
        "name": "Joshua Jaccob",
        "department": "Marketing",
        "origin": "new york",
        "employDate": "2019-08-26",
        "__v": 0,
        "attendances": {
            "2019-08-26": "Present",
            "2019-08-27": "Sick"
        }
    },

【问题讨论】:

  • 您正在尝试计算数据库中未定义的attendence.date。快速修复将是Wages.collection.countDocuments({"attendances": { "2019-08-26": "present"}}, ...rest of the code
  • 另外,搜索是区分大小写的,您必须在搜索中输入字符串的确切值。 > Wages.collection.countDocuments({"attendances": { "2019-08-26": "Present"}}
  • 感谢您的回复。我现在刚刚尝试了您的解决方案Wages.collection.countDocuments({"attendances": { "2019-08-26": "present"}},,但仍然得到“0”计数,即零
  • @A.Todkar,感谢您指出搜索的区分大小写的性质。我实际上在我的代码中使用了小写,但是,在将“present”更改为大写“Present”之后,它仍然返回零计数

标签: node.js mongodb mongoose


【解决方案1】:

如果你想在嵌入文档中按属性查找,你必须使用点符号

这不起作用,因为您要求 mongoo 查找出勤对象等于相同给定对象的文档。

{ "attendances": {"2019-08-26": "Present"}}

这只有在你的数据库中的出勤对象只包含时才有效

{ "attendances": {"2019-08-26": "Present"}}

这意味着你问 mongoo 存储的对象是否等于给定的对象,它会返回 false

 { "attendances": {"2019-08-26": "Present" , "2019-08-27": "Sick"}} ==  { "attendances": {"2019-08-26": "Present"}}

要做到这一点,你必须使用点符号

 Employee.collection.countDocuments({"attendances.2019-08-26":"Present"},(err,data)=>{
    if(err){
      res.status(500)
      res.send(err)
    }else{
      res.status(200)
      res.json(data)
    }
  })

【讨论】:

  • 终于.. 成功了!它返回一个值“> 0”,您的解释也很重要。只是想要求澄清一个额外的查询(如果你不介意的话)。如果我想在过去 30 天里让所有出席者都带有“礼物”怎么办……你能给我一个线索吗?如果你选择通过,还是谢谢!
  • 不幸的是,你不能用 mongo 查询来做到这一点,因为出席键是一个字符串而不是一个日期来检查它是否大于 X 或小于 Y,所以你必须检索所有存在的数据,然后通过构造新日期过滤此数据并检查它是否超过其他日期,我更喜欢另一个选项,您可以将架构更改为嵌入式文档{ day: Date, status: string } 的出勤数组,这将比对象类型更灵活,如果你这样做了,你可以按出勤日搜索并计算每个员工的每月出勤率,并做许多 obj 不能做的事情
  • 感谢您的提示,但说实话,我的挑战将是检索所有“存在”的数据......无论如何,我会试一试!
【解决方案2】:

由于动态日期是嵌入文档的一部分,要使用正则表达式查询该字段(用于不区分大小写的搜索),您基本上需要使用 dot notation { "attendance.2019-08-28": /present/i },使用 computed property names 构造为:

const date = "2019-08-28" // dynamic date
const query = {
    ["attendances." + date]: /present/i // computed property name
}

Employee.countDocuments(query, (err, data) => {
    if (err){
        res.status(500).send(err)
    } else{
        res.status(200).json(data)
    }
})

注意,countDocuments() 函数可以在 Mongoose 模型上直接访问。


对于日期范围查询,例如,您想要返回过去 30 天的出席人数,您需要 使用聚合框架进行查询,该框架公开了 $objectToArray$filter$size 等运算符,以便为您提供计数。

上述运算符允许您使用 $objectToArray 将出勤文档转换为键值对数组,然后您可以根据过去 30 天的条件以及使用 $filter 的“当前”值进行过滤。要获取计数,请在过滤后的数组上使用 $size 运算符。

作为说明,在文档上应用$objectToArray

{
    "2019-08-26": "Present",
    "2019-08-27": "Sick"
}

返回

[
    { "k": "2019-08-26", "v": "Present" },
    { "k": "2019-08-27", "v": "Sick" }
]

要过滤过去 n 天,您需要首先创建该范围内的日期列表,即

[
    "2019-08-27",
    "2019-08-26",
    "2019-08-25",
    ...
]

这可以在 JavaScript 中完成为

function formatDate(date) {
    var d = new Date(date),
        month = '' + (d.getMonth() + 1),
        day = '' + d.getDate(),
        year = d.getFullYear();

    if (month.length < 2) month = '0' + month;
    if (day.length < 2) day = '0' + day;

    return [year, month, day].join('-');
}


const listDatesForThePastDays = n => (
    Array(n)
        .fill(new Date())
        .map((today, i) => today - 8.64e7 * i)
        .map(formatDate)
)

这个列表可以在$filter中用作

{ "$filter": {
    "input": { "$objectToArray": "$attendances" },
    "cond": {
        "$and": [
            { "$in": ["$$this.k", listDatesForThePastDays(30)] },
            { "$eq": ["$$this.v", "Present"] }
        ]
    }
} }

并应用$size 运算符来获取计数:

{ "$size": {
    "$filter": {
        "input": { "$objectToArray": "$attendances" },
        "cond": {
            "$and": [
                { "$in": ["$$this.k", listDatesForThePastDays(30)] },
                { "$eq": ["$$this.v", "Present"] }
            ]
        }
    }
} }

您的整体查询将如下所示

function formatDate(date) {
    var d = new Date(date),
        month = '' + (d.getMonth() + 1),
        day = '' + d.getDate(),
        year = d.getFullYear();

    if (month.length < 2) month = '0' + month;
    if (day.length < 2) day = '0' + day;

    return [year, month, day].join('-');
}


const listDatesForThePastDays = n => (
    Array(n)
        .fill(new Date())
        .map((today, i) => today - 8.64e7 * i)
        .map(formatDate)
)

Employee.aggregate([
    { "$addFields": { 
        "numberofPresentAttendances": { 
            "$size": {
                "$filter": {
                    "input": { "$objectToArray": "$attendances" },
                    "cond": {
                        "$and": [
                            { "$in": ["$$this.k", listDatesForThePastDays(30)] },
                            { "$eq": ["$$this.v", "Present"] }
                        ]
                    }
                }
            }
        }
    } }
]).exec().
  .then(results => {
      console.log(results);
      // results will be an array of employee documents with an extra field numberofPresentAttendances
  })
  .catch(console.error)

要获得所有员工的计数,您需要将所有文档分组为

Employee.aggregate([
    { "$group": { 
        "_id": null,
        "totalPresent": {
            "$sum": { 
                "$size": {
                    "$filter": {
                        "input": { "$objectToArray": "$attendances" },
                        "cond": {
                            "$and": [
                                { "$in": ["$$this.k", listDatesForThePastDays(30)] },
                                { "$eq": ["$$this.v", "Present"] }
                            ]
                        }
                    }
                }
            }
        } 
    } }
]).exec()
.then(results => {
    console.log(results);
    // results will be an array of employee documents with an extra field numberofPresentAttendances
})
.catch(console.error)

【讨论】:

  • @chridam---我刚刚尝试了你的解决方案,(利用邮递员)我得到了 200K 的状态,但计数仍然为零
  • @Chrisdam 感谢您的最新编辑,现在一切都非常清楚了。我很感激!
猜你喜欢
  • 1970-01-01
  • 2023-01-02
  • 2013-09-06
  • 2021-11-24
  • 1970-01-01
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多