Mongoose Filter based on Dynamic Date Key with value

后端 未结 2 457
傲寒
傲寒 2021-01-22 08:15

I have created an employee attendance application where attendances are logged and stored in a database. I have tried to obtain a count of all date-field with the value of

相关标签:
2条回答
  • 2021-01-22 08:36

    Since the dynamic date is part of an embedded document, to query on that field with a regex expression (for case insensitive search) you essentially need to use the dot notation { "attendance.2019-08-28": /present/i }, constructed using computed property names as:

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

    Note, countDocuments() function can be accessed directly on the Mongoose model.


    For a date range query, say for example you want to return the count of attendances that were present for the last 30 days, you would need to query with the aggregation framework which exposes operators like $objectToArray, $filter and $size to give you the count.

    The above operators allow you to convert the attendances document into an array of key value pairs with $objectToArray which you can then filter based on the past 30 days criteria as well as the "present" value using $filter. To get the count, use the $size operator on the filtered array.

    As an illustration, applying $objectToArray on the document

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

    returns

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

    To filter on the past n days you will need to first create a list of dates in that range i.e.

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

    which can be done in JavaScript as

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

    This list can be used in the $filter as

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

    And apply the $size operator to get the count:

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

    Your overall query will look like

    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)
    

    To get the count for all employees then you need to group all the documents as

    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)
    
    0 讨论(0)
  • 2021-01-22 08:40

    If you want to find by property in embedded document you have to use dot notation

    this will not work, because you are asking mongoo to find the document which have attendances object equal the same given object.

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

    this will work only if attendances object in your database contains only

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

    that's mean that you asking mongoo if the stored object is equal the given object and it will return false

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

    to do this you have to use dot notation

     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 讨论(0)
提交回复
热议问题