Php MySQL-时间戳之间的平均差异,不包括周末和下班时间
我正在寻找平均时间戳之间的差异的能力,不包括周末和不包括营业时间(仅在08:00:00-17:00:00之间) 我正试图通过一个查询来实现这一点,但如果使用MySQL不可能的话,可以使用PHP函数 下面是我用来获取平均时间戳差异的当前函数 例如,下面的查询将返回星期五上午8点到星期一下午5点之间的81小时差,它需要返回18小时,因为它应该排除周末和工作日的办公时间。 我看过了,但它们似乎没有时间戳,只有日期 结果 我现在使用下面的存储函数来实现我想要的结果。它将忽略工作时间以外的时间(08:00:00-17:00:00),并忽略周末。它基本上只计算两个时间戳之间的营业时间差Php MySQL-时间戳之间的平均差异,不包括周末和下班时间,php,mysql,Php,Mysql,我正在寻找平均时间戳之间的差异的能力,不包括周末和不包括营业时间(仅在08:00:00-17:00:00之间) 我正试图通过一个查询来实现这一点,但如果使用MySQL不可能的话,可以使用PHP函数 下面是我用来获取平均时间戳差异的当前函数 例如,下面的查询将返回星期五上午8点到星期一下午5点之间的81小时差,它需要返回18小时,因为它应该排除周末和工作日的办公时间。 我看过了,但它们似乎没有时间戳,只有日期 结果 我现在使用下面的存储函数来实现我想要的结果。它将忽略工作时间以外的时间(08:0
DROP FUNCTION IF EXISTS BUSINESSHOURSDIFF;
DELIMITER $$CREATE FUNCTION BUSINESSHOURSDIFF(start_time TIMESTAMP, end_time TIMESTAMP)
RETURNS INT UNSIGNED
BEGIN
IF HOUR(start_time) > 17 THEN SET start_time = CONCAT_WS(' ', DATE(start_time), '17:00:00');
END IF;
IF HOUR(start_time) < 8 THEN SET start_time = CONCAT_WS(' ', DATE(start_time), '08:00:00');
END IF;
IF HOUR(end_time) > 17 THEN SET end_time = CONCAT_WS(' ', DATE(end_time), '17:00:00');
END IF;
IF HOUR(end_time) < 8 THEN SET end_time = CONCAT_WS(' ', DATE(end_time), '08:00:00');
END IF;
RETURN 45 * (DATEDIFF(end_time, start_time) DIV 7) +
9 * MID('0123455501234445012333450122234501101234000123450',
7 * WEEKDAY(start_time) + WEEKDAY(end_time) + 1, 1) +
TIMESTAMPDIFF(HOUR, DATE(end_time), end_time) -
TIMESTAMPDIFF(HOUR, DATE(start_time), start_time);
END $$
DELIMITER ;
DROP函数(如果存在BUSINESSHOURSDIFF);
分隔符$$CREATE函数BUSINESSHOURSDIFF(开始时间时间戳,结束时间戳)
返回无符号整数
开始
如果小时(开始时间)>17,则设置开始时间=持续时间('',日期(开始时间),'17:00:00');
如果结束;
如果小时(开始时间)<8,则设置开始时间=持续时间('',日期(开始时间),'08:00:00');
如果结束;
如果小时(结束时间)>17,则设置结束时间=持续时间('',日期(结束时间),'17:00:00');
如果结束;
如果小时(结束时间)<8,则设置结束时间=持续时间('',日期(结束时间),'08:00:00');
如果结束;
RETURN 45*(DATEDIFF(结束时间、开始时间)DIV 7+
9*MID('0123455501234445012333450122345501101234000123450',
7*工作日(开始时间)+工作日(结束时间)+1,1+
时间差(小时、日期(结束时间)、结束时间)-
时间差(小时、日期(开始时间)、开始时间);
结束$$
定界符代码>这是可能的,但仅使用sql非常难看。然而,如果您可以使用存储函数,那么它看起来也非常漂亮
根据您在问题中链接的SO问题,我们知道以下表达式计算两个日期之间的工作日数:
5 * (DATEDIFF(@E, @S) DIV 7) +
MID('0123455501234445012333450122234501101234000123450',
7 * WEEKDAY(@S) + WEEKDAY(@E) + 1, 1)
如果我们将这个表达式乘以9,即#每个工作日的工作时间,我们得到营业时间差
。将两个时间戳之间的小时数调整相加,我们可以得到最终的表达式,然后对其求平均值
45 * (DATEDIFF(@E, @S) DIV 7) +
9 * MID('0123455501234445012333450122234501101234000123450',
7 * WEEKDAY(@S) + WEEKDAY(@E) + 1, 1) +
TIMESTAMPDIFF(HOUR, DATE(@E), @E) -
TIMESTAMPDIFF(HOUR, DATE(@S), @S)
因此,丑陋但有效的查询是:
SELECT
clients.name
, AVG(45 * (DATEDIFF(jobs.time_updated, jobs.time_created) DIV 7) +
9 * MID('0123455501234445012333450122234501101234000123450',
7 * WEEKDAY(jobs.time_created) + WEEKDAY(jobs.time_updated) + 1, 1) +
TIMESTAMPDIFF(HOUR, DATE(jobs.time_updated), jobs.time_updated) -
TIMESTAMPDIFF(HOUR, DATE(jobs.time_created), jobs.time_created)) AS average_response
, AVG(45 * (DATEDIFF(jobs.time_closed, jobs.time_created) DIV 7) +
9 * MID('0123455501234445012333450122234501101234000123450',
7 * WEEKDAY(jobs.time_created) + WEEKDAY(jobs.time_closed) + 1, 1) +
TIMESTAMPDIFF(HOUR, DATE(jobs.time_closed), jobs.time_closed) -
TIMESTAMPDIFF(HOUR, DATE(jobs.time_created), jobs.time_created)) AS average_closure
, COUNT(jobs.id) AS ticket_count
, SUM(time_total) AS time_spent
FROM jobs
LEFT JOIN clients ON jobs.client = clients.id
WHERE jobs.status = 'closed'
GROUP BY jobs.client
更好的替代方法是创建一个存储函数来处理business hours diff
逻辑
DROP FUNCTION IF EXISTS BUSINESSHOURSDIFF;
DELIMITER $$ CREATE FUNCTION BUSINESSHOURSDIFF(start_time TIMESTAMP, end_time TIMESTAMP)
RETURNS INT UNSIGNED
BEGIN
RETURN 45 * (DATEDIFF(end_time, start_time) DIV 7) +
9 * MID('0123455501234445012333450122234501101234000123450',
7 * WEEKDAY(start_time) + WEEKDAY(end_time) + 1, 1) +
TIMESTAMPDIFF(HOUR, DATE(end_time), end_time) -
TIMESTAMPDIFF(HOUR, DATE(start_time), start_time);
END $$
DELIMITER ;
然后根据需要调用它
SELECT
clients.name
, avg(BUSINESSHOURSDIFF(jobs.time_created, jobs.time_updated)) AS average_response
, avg(BUSINESSHOURSDIFF(jobs.time_created, jobs.time_closed)) AS average_closure
, count(jobs.id) AS ticket_count
, SUM(time_total) AS time_spent
FROM jobs
LEFT JOIN clients ON jobs.client = clients.id
WHERE jobs.status = 'closed'
GROUP BY jobs.client;
好吧,使用MySQL@variables可能会伤到你的头脑。它们的工作方式类似于内联程序语句,当您通过:=,分配时,它们可以用于下一个被查询的sql列中,从而简化了逻辑,而无需一直使用繁重的日期数学
首先,这里是整个查询。然后我会把它分解
select
pq.id,
pq.client,
c.name,
sum( pq.UpdHours ) as ResponseHours,
sum( pq.dayHours ) as TotHours,
sum( pq.TimeOnlyOnce ) as TotalTime
from
(select
j.id,
j.client,
j.time_created,
j.time_updated,
if( jdays.DaySeq = 0, time_total, 0 ) as TimeOnlyOnce,
@justDay := date_add( date( j.time_created ), interval jdays.DaySeq day ) as JustTheDay,
@dtS := date_add( @justDay, interval 8 hour ) as StoreOpen,
@dtE := date_add( @justDay, interval 17 hour ) as StoreClosed,
@isWkDay := IF( DAYOFWEEK(@justDay) in ( 1, 7 ), 0, 1 ) as IsWeekDay,
@dtST := greatest( j.time_created, @dtS ) as StartTime,
@dtUpd := least( j.time_updated, @dtE ) as TimeUpdate,
@dtET := least( j.time_closed, @dtE ) as EndTime,
if( @isWkDay, TIMESTAMPDIFF( HOUR, @dtST, @dtUpd ), null ) as UpdHours,
if( @isWkDay, TIMESTAMPDIFF( HOUR, @dtST, @dtET ), null ) as dayHours,
jdays.DaySeq
from
jobs j
JOIN ( select @dayLoop := @dayLoop +1 as DaySeq
from jobs js,
( select @dayLoop := -1 ) sqlvars
limit 10 ) jdays
ON jdays.DaySeq <= TIMESTAMPDIFF( DAY, j.time_created, j.time_closed),
( select
@justDay := '2016-01-01',
@dtS := @justDay,
@dtE := @justDay,
@dtST := @justDay,
@dtET := @justDay,
@dtUpd := @justDay,
@isWkDay := 0) sqlvars2
order by
j.id,
j.client,
jdays.DaySeq) pq
LEFT JOIN clients c
ON pq.client = c.id
group by
pq.id
这将构建一个子表别名“jdays”,以表示从0到10天的一天序列(如果任何单个活动需要超过10天,只需扩展限制)。我以-1开始@dayLoop,因此当加入到jobs表时(假设实际上它有10多条记录),它将获取10行,分别为0、1、2、--9。这就避免了需要一些虚假的表来表示一个给定作业可能运行的总时间,即运行时的记录数
接下来是JOBS表(上面表示多天的子查询,它有意创建笛卡尔结果)和下一部分之间的连接
( select
@justDay := '2016-01-01',
@dtS := @justDay,
@dtE := @justDay,
@dtST := @justDay,
@dtET := @justDay,
@dtUpd := @justDay,
@isWkDay := 0) sqlvars2
它只会创建一些变量,这些变量仅表示所讨论的日期、商店营业时间/结束时间(上午8点/下午5点)、特定的票证id开始/结束时间(如果在两天之间继续),以及更新时间(响应时间),如果所讨论的日期是否为一周,则会创建一个标志列。这只是在sql语句中声明变量,而不需要外部声明
现在是下一级查询,我在其中使用了所有@variables。可以将其视为一次分析一行,并根据jDays别名结果得到笛卡尔结果
我只想看一下你的第二张票证
ID Client time_created time_updated time_closed time_total
6412 106 2016-03-04 08:00:00 2016-03-07 08:00:00 2016-03-07 17:00:00 .25
select
QryPerID.client,
QryPerID.name,
avg( QryPerID.ResponseHours ) as AvgResponseHours,
avg( QryPerID.TotHours ) as AvgTotHours,
sum( QryPerID.TotalTime ) as TotalTime,
count(*) as ClientTickets
from
( entire previous query ) QryPerID
group by
QryPerID.client,
QryPerID.name
如果单独运行这个内部查询,那么对于这个ID,jDays表的联接是基于从创建到关闭的总天数大于jDays值(例如0、1、2、3等等)。为什么要创建多行?因为每一天都需要根据自己的优点来评估。因此,一次只计算一个数据元素,我只计算一次总的_时间记录,因此它基于daySeq=0的IF(),所以在对不同行进行拆分时,它不会被计算多次。(3月4日、5日、6日和7日)
现在是日期。就为了露齿而笑,让我们假设我们所创造的时间实际上是午后的某个值,比如2016-03-04 13:15:00(下午1:15)。我只想要一天的时间。Date(j.time_created)只返回日期部分
@justDay := date_add( date( j.time_created ), interval jdays.DaySeq day ) as JustTheDay,
结果公布于“2016-03-04”。现在,我分别添加了8小时和17小时来计算商店的开门和关门时间,结果如下所示,如果周末与否,还会加上一个标志
@dtS := date_add( @justDay, interval 8 hour ) as StoreOpen,
@dtE := date_add( @justDay, interval 17 hour ) as StoreClosed,
@isWkDay := IF( DAYOFWEEK(@justDay) in ( 1, 7 ), 0, 1 ) as IsWeekDay,
JustTheDay StoreOpen (8am) StoreClosed (5pm)
2016-03-04 2016-03-04 8:00:00 2016-03-04 17:00:00
根据给定日期的这些基线值(将在3月5日、6日和7日重复),我们现在想知道票证时间何时开始、何时更新和何时结束(关闭)。因此,开始时间是创建的时间或一天的开始时间中较大的一个。在根据我的示例修改的开始时间中,票证的开始时间实际上是下午1:15,而不是原始数据的上午8点,这只是为了给出上下文。更新时间和结束时间基于最短时间。所以,由于更新和关闭的时间是在周末后的周一,我们希望将时钟停在某一天(3月4日)的下午5点。类似的时间结束
现在对于正在处理的每一行,我可以使用这些开始,更新,
@justDay := date_add( date( j.time_created ), interval jdays.DaySeq day ) as JustTheDay,
@dtS := date_add( @justDay, interval 8 hour ) as StoreOpen,
@dtE := date_add( @justDay, interval 17 hour ) as StoreClosed,
@isWkDay := IF( DAYOFWEEK(@justDay) in ( 1, 7 ), 0, 1 ) as IsWeekDay,
JustTheDay StoreOpen (8am) StoreClosed (5pm)
2016-03-04 2016-03-04 8:00:00 2016-03-04 17:00:00
@dtST := greatest( j.time_created, @dtS ) as StartTime,
@dtUpd := least( j.time_updated, @dtE ) as TimeUpdate,
@dtET := least( j.time_closed, @dtE ) as EndTime,
if( @isWkDay, TIMESTAMPDIFF( HOUR, @dtST, @dtUpd ), null ) as UpdHours,
if( @isWkDay, TIMESTAMPDIFF( HOUR, @dtST, @dtET ), null ) as dayHours,
select
QryPerID.client,
QryPerID.name,
avg( QryPerID.ResponseHours ) as AvgResponseHours,
avg( QryPerID.TotHours ) as AvgTotHours,
sum( QryPerID.TotalTime ) as TotalTime,
count(*) as ClientTickets
from
( entire previous query ) QryPerID
group by
QryPerID.client,
QryPerID.name
CREATE TABLE BusinessDays (
day DATE NOT NULL,
PRIMARY KEY (day)
) ENGINE=InnoDB;
-- Worry about intervening days:
SELECT @days := COUNT(*) - 2
FROM YourTable yt
JOIN BusinessDays a ON a.day >= DATE(yt.start_dt)
JOIN BusinessDays z ON z.day <= DATE(yt.end_dt);
-- Get hours in first and last days:
SELECT @secs := TIME_TO_SEC(TIMEDIFF(TIME(start_dt), '08:00:00')) +
TIME_TO_SEC(TIMEDIFF('17:00:00', TIME(end_dt)))
FROM YourTable;
-- Generate answer:
SELECT @days * 9 + @secs/3600 AS 'Hours';