【问题标题】:MySQL - PHP: Calculate total hours in a day between multiple eventsMySQL - PHP:计算一天中多个事件之间的总小时数
【发布时间】:2016-11-02 19:44:16
【问题描述】:

我有一张名为 time_track 的表:

+----+--------+---------------------+---------+
| id | emplid | ctimestamp          | eventid |
+----+--------+---------------------+---------+
| 1  | 13     | 2016-06-02 03:41:41 | 1       |
+----+--------+---------------------+---------+
| 2  | 13     | 2016-06-02 09:04:49 | 2       |
+----+--------+---------------------+---------+
| 3  | 13     | 2016-06-02 10:03:13 | 1       |
+----+--------+---------------------+---------+
| 4  | 13     | 2016-06-02 13:21:23 | 2       |
+----+--------+---------------------+---------+

eventid 1 = Start workeventid 2 = Stop work 的位置。

考虑到工作时间是所有 eventid 的 1 和 2 之间的总小时数,我如何计算任何一天的小时数 - WHERE emplid = 13 AND Year(ctimestamp) = 2016 and Month(ctimestamp) = 06 and Day(ctimestamp) = 02

【问题讨论】:

  • total hours in a day 是什么意思?您是指两个时间戳之间的总小时数吗?
  • 我认为这是一个时间表 - 所以 Fred 在 03:41 打卡上班,一直工作到 09:04 ... 小睡了一个小时,然后在 10:03 到 13:21 再次打卡 -所以这是累计金额。但是,如果它正在按天分解,则 21:00 -> 04:00 将导致问题,因为给定日期将有 stop work 事件但没有前面的 开始工作事件。
  • 这是一个经常针对数据库运行的查询,因此查询性能至关重要?如果是这样,您愿意考虑替代模式吗?您打算如何解决上述@CD001 指出的边缘条件或存在不成对的结束/开始时间(即数据不完整)的情况?
  • 刚刚编辑了我的答案:添加了一个函数来计算总小时数。
  • @Devon 我的意思是总工作时间,如果 1 是工作开始,2 是工作结束。事件 1 和 2 一天可以发生多次,所以我想要总工作时间

标签: php mysql date datetime


【解决方案1】:

选择 UNIX_TIMESTAMP('2010-11-29 13:16:55') - UNIX_TIMESTAMP('2010-11-29 13:13:55') 作为输出

$entry_time = [...在这里使用查询...]

$exit_time = [...使用查询在此处获取...]

$query = " SELECT UNIX_TIMESTAMP(' ".$entry_time." ') - UNIX_TIMESTAMP(' ".$exit_time." ') as days;";

【讨论】:

  • 为什么要使用 SQL 减去两个 PHP 变量?
  • 虽然是 php 值,但它们 replace variable namecontents (这里是 contenttimestamp),将其存储到变量 $query AS ** String ** 中。然后像 $query = SELECT UNIX_TIMESTAMP(2010-11-29 13:16:55) - UNIX_TIMESTAMP(2010-11-29 13:13:55) 这样的值作为天数;将存储在变量中,这将传递给 sql! @PaulH
  • 是的,但为什么要使用 SQL?为什么不只写 PHP:$query = $exit_time - $entry_time。在这里使用 SQL 就像坐车走 2 米或 2 码。
  • $查询是sql的语法!不是PHP!你可以试试看:link 如果你的 php 服务器(apache)是打开的! ` "SELECT UNIX_TIMESTAMP(2010-11-29 13:16:55) - UNIX_TIMESTAMP(2010-11-29 13:13:55)" ` 结果如下: ` |输出 | |180 |作为单个单元格表中的单元格:您可以尝试@PaulH
  • 是的,我可以使用 SQL 进行减法,是的,$query 包含一个 SQL 字符串,但 $query = "SELECT 5 - 4"; 是一个 PHP 语句,它将一个包含 SQL 语句的字符串分配给一个 PHP 变量。
【解决方案2】:

与下面的仅 SQL 答案不同,将 SQL 和 PHP 结合起来以提高可读性和性能非常有效。此外,PHP 将允许您编写代码来处理丢失或重复数据等异常。

<?php
// Generate test data like it would be returned by a PDO::fetchAll(PDO:::FETCH_ASSOC) with
// SELECT * FROM time_track 
//    WHERE emplid = 13 AND Year(ctimestamp) = 2016 and Month(ctimestamp) = 06 and Day(ctimestamp) = 02 
//    ORDER BY ctimestamp
$logs[] = ['ctimestamp'=>'2016-06-02 03:41:41', 'eventid'=>1];
$logs[] = ['ctimestamp'=>'2016-06-02 09:04:49', 'eventid'=>2];
$logs[] = ['ctimestamp'=>'2016-06-02 10:03:13', 'eventid'=>1];
$logs[] = ['ctimestamp'=>'2016-06-02 13:21:23', 'eventid'=>2];

// Compute working time
$worktime = 0; // in seconds
foreach ($logs as $log) {
    if ($log['eventid'] == 1) {  // start work
        $startWork = $log['ctimestamp'];
    } else { // end work
        $endWork = $log['ctimestamp'];
        $worktime += strtotime($endWork) - strtotime($startWork);
    }
}
echo gmdate('H:i', $worktime); // seconds to hours:minutes
?>

运行代码:http://phpfiddle.org/main/code/jx8h-ztuy

以下是上述代码的经过测试的改进,包括数据库访问和循环。
由于您表示性能至关重要,您可能需要使用PDO::prepare()

<pre>
<?php
class TimeLog {
    private $pdo;
    private $pdoStatement;

    // constructor: connect to database and prepare statement
    function __construct(){
        // adapt following constructor to your database configuartion
        $this->pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'username', 'password'); 
        $this->pdoStatement = $this->pdo->prepare(
            'SELECT * FROM time_track 
                WHERE emplid = :emplid 
                AND DATE(ctimestamp) = :cdatestamp
                ORDER BY ctimestamp
        ');
    }

    // compute workTime for given employee and date
    function workTime($emplid, $cdatestamp) {
        // fetch from database, executing prepared statement
        $this->pdoStatement->execute([':emplid'=>$emplid, ':cdatestamp'=>$cdatestamp]);
        $logs = $this->pdoStatement->fetchAll(PDO::FETCH_ASSOC);

        // compute working time
        $worktime = 0; // in seconds
        foreach ($logs as $log) {
            if ($log['eventid'] == 1) {  // start work
            $startWork = $log['ctimestamp'];
            } else { // end work
                $endWork = $log['ctimestamp'];
                $worktime += strtotime($endWork) - strtotime($startWork);
            }
        }
        return gmdate('H:i', $worktime); // convert seconds to hours:minutes
    }
}

$timeLog = new Timelog(); // invoke constructor once

$emplid = 13; // example

// echo registration of last seven days
for ($date = strtotime('-7 days'); $date <= time(); $date += 24 * 3600) {
    // convert $date to YYYY-MM-DD format
    $cdatestamp = date('Y-m-d', $date); 
    // execute one SQL statement and return computed worktime
    $workTime = $timeLog->workTime($emplid, $cdatestamp); 
    // show result
    echo $cdatestamp, "\t", $workTime, "\n";
}
?>

【讨论】:

  • 您应该提供一些解释以及代码示例。
【解决方案3】:

您也可以使用 PHP(而不是 SQL):

<?php
$data = array( array( "1","2016-06-02 03:41:41" ),
               array( "2","2016-06-02 09:04:49" ),
               array( "1","2016-06-02 10:03:13" ),
               array( "2","2016-06-02 13:21:23" )
             );
$hours = 0;
foreach ( $data as $row ) // PROCESS ALL ROWS FROM QUERY.
{ if ( $row[ 0 ] == "1" ) // IF CURRENT ROW IS START TIME
       $start = strtotime( $row[ 1 ] );
  else { $stop = strtotime( $row[ 1 ] ); // STOP TIME. CALCULATE.
         $hours += ( $stop - $start ) / 3600;
       }
}
echo $hours; // 8.6883333333333.
?>

您可以对结果进行四舍五入。

将之前的代码复制粘贴到一个文件中,将其保存为 .PHP 并在浏览器中打开它。随意更改示例数据。

编辑:调用函数来计算所有时间更容易:

<?php

function total_hours ( $data )
{ $hours = 0;
  foreach ( $data as $row )
    if ( $row[ "eventid" ] == "1" )
         $start = strtotime( $row[ "ctimestamp" ] );
    else { $stop = strtotime( $row[ "ctimestamp" ] );
           $hours += ( $stop - $start ) / 3600;
         }
  return $hours;
}

$sample_data = array( array( "id"         => 1,
                             "emplid"     => 13,
                             "ctimestamp" => "2016-06-02 03:41:41",
                             "eventid"    => 1 ),
                      array( "id"         => 2,
                             "emplid"     => 13,
                             "ctimestamp" => "2016-06-02 09:04:49",
                             "eventid"    => 2 ),
                      array( "id"         => 3,
                             "emplid"     => 13,
                             "ctimestamp" => "2016-06-02 10:03:13",
                             "eventid"    => 1 ),
                      array( "id"         => 4,
                             "emplid"     => 13,
                             "ctimestamp" => "2016-06-02 13:21:23",
                             "eventid"    => 2 )
                    );
echo total_hours( $sample_data ); // 8.6883333333333.
?>

使用从查询中获得的 sql-result 作为参数调用此函数(并将 foreach 替换为 while ( $row = mysqli_fetch_array )。

【讨论】:

    【解决方案4】:

    您需要计算事件 1 和事件 2 的时间戳之间的差异。这确实假设对于任何给定日期,每个 empl_id 只有 2 个事件,并且结帐时间在同一天内(例如,此查询中不会显示通宵时间)。这不是一个非常强大的解决方案,但我不确定您的数据的完整性以及您需要处理的边缘情况。

    SELECT TIMEDIFF(t1.c_timestamp, t2.c_timestamp) / 3600 AS hourDiff
    FROM time_track t1
    INNER JOIN time_track t2 
    ON (t2.id > t1.id AND t2.event_id = 2 AND t1.empl_id = t2.empl_id AND DAY(t2.c_timestamp) = DAY(t1.c_timestamp) AND MONTH(t2.c_timestamp) = MONTH(t1.c_timestamp) AND YEAR(t2.c_timestamp) = YEAR(t1.c_timestamp))
    WHERE t1.event_id = 1 AND t1.empl_id = 13 AND Year(t1.c_timestamp) = 2016 and Month(t1.c_timestamp) = 6 and Day(t1.c_timestamp) = 2
    

    【讨论】:

      【解决方案5】:

      您必须在子程序中自行加入您的表,并获得大于 eventid=1 的 eventid=2 的最小值,并计算这 2 条记录之间的差异。在外部查询中,您按 eventid=1 时间戳的日期总结差异:

      select date(t3.ctimestamp), t3.emplid, sum(diff) / 60 as hours_worked
      from
          (select t1.id, t1.ctimestamp, t1.emplid, min(t2.ctimestamp) as mintime, timestampdiff(minute,min(t2.ctimestamp), t1.ctimestamp) as diff 
           from yourtable t1
           inner join yourtable t2 on t1.ctimestamp<t2.ctimestamp
           where t1.eventid=1
                 and t2.eventid=2
                 and t1.emplid=13
                 and t2.emplid=13
                 and date(t1.ctimestamp)='2016-06-02' --may have checked out the next day, I do not know how you want to handle that
           group by t1.id, t1.ctimestamp, t1.emplid) t3
      group by date(t3.ctimestamp)
      

      在实时环境中,我不会将解决方案基于没有间隙的 id 列,即使它是一个自动增量列。通常会有间隙。如果您有孤儿签入或签出事件,您还需要决定会发生什么。我的代码假定每次签入都有相应的签出。

      【讨论】:

        【解决方案6】:

        你可以试试这样的

        如果您需要进一步分离数据,也可以按年和月分组。

        select day(ctimestamp) as Day, hour(ctimestamp) as Hour, count(*) as Count
        from MyTable
        where ctimestamp between :date1 and :date2
        group by day(ctimestamp), hour(ctimestamp)
        

        【讨论】:

        • 你应该对你的方法提供一些解释。
        【解决方案7】:

        查询:SELECT DATEDIFF('2010-10-08 18:23:13', '2010-09-21 21:40:36') AS days;

        $time_entry = [...] // 查找查询并保存到这个变量中

        $time exit = [...] // 查找查询并将输出保存到该变量中

        $query = "SELECT DATEDIFF('"。 time_entry ."', '".time_exit."') AS days;"

        【讨论】:

        • 你应该对你的方法提供一些解释
        • 虽然它是 php 值,但它们将变量名称替换为内容(这里的内容是时间戳),并将其存储到变量 $query AS ** String ** 中。然后像 $query = SELECT UNIX_TIMESTAMP(2010-11-29 13:16:55) - UNIX_TIMESTAMP(2010-11-29 13:13:55) 这样的值作为天数;将存储在变量中,这将传递给 sql! [@MikeBrant]
        猜你喜欢
        • 2021-11-06
        • 1970-01-01
        • 2012-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-25
        • 1970-01-01
        • 2021-01-03
        相关资源
        最近更新 更多