简体   繁体   中英

calculate the average number of hours per day that the employee spent on the workplace for the previous month

I have table Time_Periods (Empl_Name, Direction, Date_Time) , which stores data on the entry and exit of employees.

Empl_Name - the name of the employee;

Direction - Direction (Input or Output);

Date_Time - The date and time the action was committed (detailed until a second).

If the employee enters, then he necessarily leaves on the same day. Within one day the employee can repeatedly enter and leave.

How to write a request that will calculate the average number of hours per day that the employee spent on the workplace for the previous month?

UPDATE: Example of table.

+-----------+-----------+---------------------+
| Empl_Name | Direction | Date_Time           |
+-----------+-----------+---------------------+
| a1        |         1 | 2017-03-18 23:55:59 |
| a1        |         0 | 2017-03-18 23:56:07 |
| a2        |         1 | 2017-03-18 23:56:17 |
| a2        |         0 | 2017-03-18 23:56:22 |
| a3        |         1 | 2017-03-18 23:57:35 |
| a3        |         0 | 2017-03-18 23:57:39 |
+-----------+-----------+---------------------+

Expected output: a1 spent on the workplace 7/30 secs, a2 spent 5/30 secs, a3 spent 4/30 secs. That is mean that I want to sum work time for every employee and divide by 30.

You want to do a query treating the same table as if it were two different tables - as follows:

SELECT t1.Empl_Name, TIMEDIFF(t1.Date_Time,t2.Date_Time) AS timein 
FROM empl t1, empl t2 
WHERE t1.Direction='0' AND t2.Direction='1' AND t1.Empl_Name=t2.Empl_Name  
GROUP BY Empl_Name

Results:

Empl_Name timein
A1        00:00:08
A2        00:00:05
A3        00:00:04

Those are the times in seconds. You can what you want with those. As far as multiple times in a day, your database does not specifically say which in and out times go together. If one can assume consecutive in/out, in/out, then you'd need SUM the times and ORDER BY Date_Time but there is no clear way to do this without a very complicated sub-query.

Consider a join of derived tables matching Empl_Name and Date (latter pulled from datetime field). Each derived table accounts for Time In records and Time Out records respectively.

Also, a TimeRank is integrated for those multiple records per day since a join by date outputs all same day combinations but we only want time period pairings (start/end) joined within a day. Hence, a rank specifies the first, second, and onward times employee checks in and out, incrementing across all days. Once the two sets are joined then outer query aggregates grouping by Empl_Name and Date for average or total time differences by day.

SELECT p1.Empl_Name, DATE_FORMAT(p1.Date_Time_In,'%Y-%m-%d') AS `TimeInDate`, 
       AVG(TIMEDIFF(p1.Date_Time_In, p2.Date_Time_Out)) AS `AvgTimeDiffSecs`,
       SUM(TIMEDIFF(p1.Date_Time_In, p2.Date_Time_Out)) AS `SumTimeDiffSecs` 
FROM
   -- TIME-IN RECORDS
   (SELECT t.Empl_Name, DATE_FORMAT(t.Date_Time,'%Y-%m-%d') AS `TimeInDate`, 
           t.Date_Time As `Date_Time_In`,
           (SELECT Count(*) FROM Time_Periods sub 
            WHERE sub.Date_Time <= t.Date_Time 
            AND sub.Empl_Name = t.Empl_Name
            AND sub.Direction = 0) As TimeInRank       
    FROM Time_Periods t
    WHERE t.Direction=0) As p1

INNER JOIN
   -- TIME-OUT RECORDS
   (SELECT t.Empl_Name, DATE_FORMAT(t.Date_Time,'%Y-%m-%d') AS `TimeInDate`, 
           t.Date_Time As `Date_Time_Out`,
           (SELECT Count(*) FROM Time_Periods sub 
            WHERE sub.Date_Time <= t.Date_Time 
            AND sub.Empl_Name = t.Empl_Name
            AND sub.Direction = 1) As TimeOutRank       
    FROM Time_Periods t
    WHERE t.Direction=1) As p2

ON p1.Empl_Name = p2.Empl_Name AND p1.TimeInRank = p2.TimeOutRank
AND DATE_FORMAT(p1.Date_Time_In,'%Y-%m-%d') = DATE_FORMAT(p2.Date_Time_Out,'%Y-%m-%d')

GROUP BY p1.Empl_Name, DATE_FORMAT(p1.Date_Time_In,'%Y-%m-%d')

-- Empl_Name    TimeInDate  AvgTimeDiffSecs     SumTimeDiffSecs
--        a1    2017-03-18     8.0000000000            8.000000
--        a2    2017-03-18     5.0000000000            5.000000
--        a3    2017-03-18     4.0000000000            4.000000

One possible approach is to start with a list of coming and leaving, grouped by employees:

SELECT e.Empl_Name, a.Date_Time coming, b.Date_Time leaving, TIMEDIFF(b.Date_Time, a.Date_Time) AS timein 
FROM empl e
    INNER JOIN empl a ON e.Empl_Name = a.Empl_Name AND a.Direction = 1
    INNER JOIN empl b ON e.Empl_Name = b.Empl_Name AND b.Direction = 0 AND a.Date_Time < b.Date_Time
GROUP BY e.Empl_Name, a.Date_Time 

Would give something like:

+-----------+---------------------+---------------------+----------+
| Empl_Name |       coming        |       leaving       |  timein  |
+-----------+---------------------+---------------------+----------+
| a1        | 2017-03-18 23:55:59 | 2017-03-18 23:56:07 | 00:00:08 |
| a1        | 2017-03-18 23:58:08 | 2017-03-18 23:58:37 | 00:00:29 |
| a2        | 2017-03-18 23:56:17 | 2017-03-18 23:56:22 | 00:00:05 |
| a2        | 2017-03-18 23:58:03 | 2017-03-19 01:30:36 | 01:32:33 |
| a3        | 2017-03-18 23:57:35 | 2017-03-18 23:57:39 | 00:00:04 |
+-----------+---------------------+---------------------+----------+

http://rextester.com/EMZ27244

In the next step we could wrap it in a subquery to calculate the total hours and the average hours per day for the previous month :

SELECT 
    t.Empl_Name, 
    YEAR(CURRENT_DATE - INTERVAL 1 MONTH) last_month_year,
    MONTHNAME(CURRENT_DATE - INTERVAL 1 MONTH) last_month,
    COUNT(DISTINCT DATE(t.coming)) total_days_at_work,
    TIME_TO_SEC(SUM(t.timein)) total_seconds,
    TIME_TO_SEC(SUM(t.timein))/3600 total_hours,
    TIME_TO_SEC(SUM(t.timein))/3600/COUNT(DISTINCT DATE(t.coming)) avg_hours_per_day_worked,
    TIME_TO_SEC(SUM(t.timein))/3600/DAY(LAST_DAY(CURRENT_DATE - INTERVAL 1 MONTH)) avg_hours_per_day_prev_month
    FROM (
        SELECT e.Empl_Name, a.Date_Time coming, b.Date_Time leaving, TIMEDIFF(b.Date_Time, a.Date_Time) AS timein 
        FROM empl e
            INNER JOIN empl a ON e.Empl_Name = a.Empl_Name AND a.Direction = 1
            INNER JOIN empl b ON e.Empl_Name = b.Empl_Name AND b.Direction = 0 AND a.Date_Time < b.Date_Time
        GROUP BY e.Empl_Name, a.Date_Time 
    ) t
WHERE YEAR(t.coming) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(t.coming) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)
GROUP BY t.Empl_Name;

Result:

+-----------+-----------------+------------+--------------------+---------------+-------------+--------------------------+------------------------------+
| Empl_Name | last_month_year | last_month | total_days_at_work | total_seconds | total_hours | avg_hours_per_day_worked | avg_hours_per_day_prev_month |
+-----------+-----------------+------------+--------------------+---------------+-------------+--------------------------+------------------------------+
| a1        |            2017 | February   |                  1 |            37 |      0.0103 |               0.01027778 |                   0.00036706 |
| a2        |            2017 | February   |                  2 |         22538 |      6.2606 |               3.13027778 |                   0.22359127 |
| a3        |            2017 | February   |                  1 |             4 |      0.0011 |               0.00111111 |                   0.00003968 |
+-----------+-----------------+------------+--------------------+---------------+-------------+--------------------------+------------------------------+

http://rextester.com/FNQH14731

EDIT: Updated the example query to also include the total number of days the employee was at work the previous month.

avg_hours_per_day_worked shows the average number of hours per worked day the previous month.

avg_hours_per_day_prev_month shows the average number of hours per day based on the total number of days the previous month. For example if the previous month is January, the hours are divided by 31. If Februari, divided by 28 and so on.

Here is the code,

  1. This solves the multiple ins and outs within a day
  2. Also, it compares the first in and the last out for the same day

This is done by taking the first record as the in time and the last record as the out time and using date (and not datetime) while joining the tables

select t1.Empl_Name, 
       sum (Seconds_per_day)/count(distinct date_id) as average_seconds
from 
( 
SELECT t1.Empl_Name, 
       t1.cast(t1.Date_Time AS DATE) as date_id, 
       DATEDIFF(second,min(t1.Date_Time),max(t2.Date_Time)) AS Seconds_per_day 
FROM empl as t1
inner join  empl as t2 
on t1.Direction='0' AND t2.Direction='1' 
   AND t1.Empl_Name=t2.Empl_Name
   and CAST(t1.Date_Time AS DATE) = CAST(t2.Date_Time AS DATE)
GROUP BY Empl_Name, t1.cast(t1.Date_Time AS DATE)
)
group by  t1.Empl_Name

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM