【问题标题】:Calculate two records and join them in the same MySQL row计算两条记录并将它们连接到同一 MySQL 行中
【发布时间】:2018-05-14 05:37:35
【问题描述】:

我有一个表,其中包含不同行中员工的访问控制、办公室出入口的数据。 输出基于日期和时间范围示例中的查询 ac_date >= '2018-05-12' AND ac_date <= '2018-05-13' AND ac_time >='08:00:00' AND ac_time <= '13:00:00']

表 AC

CREATE TABLE `AC` (
  `employee` int(11) NOT NULL,
  `ac_date` date NOT NULL,
  `ac_time` time NOT NULL,
  `ac_event` tinyint(4) NOT NULL,
  KEY `index2` (`employee`,`ac_date`,`ac_time`,`ac_event`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Employee     ac_date        ac_time     ac_event
2032        2018-05-12      08:52:00        1
2032        2018-05-12      11:39:33        0
2032        2018-05-12      11:48:06        1
2032        2018-05-12      11:52:54        0
2032        2018-05-12      11:59:54        1
2032        2018-05-12      12:23:40        0
2032        2018-05-13      08:34:43        1
2032        2018-05-13      09:02:25        0
2032        2018-05-13      09:12:16        1
2032        2018-05-13      11:45:21        0
2032        2018-05-13      12:50:40        1
2032        2018-05-13      12:52:16        0

其中 ac_ event = 1 是输入,ac event = 0 是输出。

我需要进行一个查询,在同一行(进入/退出)中显示数据,然后计算员工离开办公室的时间。 乙:

第一个块的输出:

Employee    entry_date   exit_date      entry_date   exit_date  duration 
2032        2018-05-12   2018-05-12     08:52:00     11:39:33   02:47:33

我能够通过对同一张表进行查询来获得结果,但是我有重复的数据,所以我必须求助于 [ac_date, ac_time] 组。我不知道我尝试的方式是否正确,所以我想看看专家的解决方案。谢谢!

更新: http://sqlfiddle.com/#!9/6f36f/3

【问题讨论】:

  • 请添加您已经写好的查询
  • 您想如何处理第 3、4、5 和 6 行,因为它们属于同一员工且在同一日期?我的查询将考虑员工一天的首次登录和最后一次注销,并计算时间差。即每位员工每天一行

标签: mysql sql database


【解决方案1】:

这是原始查询的简化版本:

select AC.employee, AC.ac_date, AC.ac_time,
   min(AC2.ac_time) as exit_time,
   timediff(min(AC2.ac_time), AC.ac_time)
from AC
left join AC as AC2
  on AC2.ac_date = AC.ac_date 
 and AC2.ac_time > ac.ac_time
 and AC2.ac_event = 0
where AC.ac_event = 1
  AND AC.ac_date >= '2018-05-11'
  AND AC.ac_date <= '2018-05-13'
  AND AC.ac_time >= '00:00:00'
  AND AC.ac_time <= '23:59:00'
group by AC.employee, AC.ac_date, AC.ac_time
order by AC.employee, AC.ac_date, AC.ac_time
;

fiddle

但是不管你怎么写,它总是一种非等连接(即不基于=),如果没有匹配索引,性能可能会很差。

顺便说一句,如果这是 MariaDB 或 MySQL 8,这对于 LAG/LEAD 来说将是一项简单的任务。

【讨论】:

  • 感谢您的回答,@dnoeth 正如我之前所说,即使您的回答是解决方案优化器认为它效率不高。这种方法认为它更优化,成本低得多。 sqlfiddle.com/#!9/6f36f/3
【解决方案2】:

试试这个:

SELECT 
    *, 
    TIMEDIFF(exit_time,entry_time) as duration
FROM
    (
        SELECT 
            Employee, 
            ac_date as entry_date,
            ac_date as exit_date,
            MIN(CASE WHEN ac_event=1 THEN ac_time END) entry_time,
            MAX(CASE WHEN ac_event=0 THEN ac_time END) exit_time
        FROM
            AC
        GROUP BY 
            Employee,
            ac_date
    ) as t

【讨论】:

  • 这只会为每个employee, ac_date 提供一个条目。我认为 OP 实际上想要计算 连续 进入/退出点之间的持续时间。从提供的示例数据来看,我们可以在一天内有多个进入 - 退出对。
【解决方案3】:

您可以使用相关子查询来获取进入/退出对:

SELECT employee, ac_date AS entry_date,
       (SELECT ac_date 
       FROM AC AS i
       WHERE i.ac_event = 0 
             AND i.employee = t.employee 
             AND i.ac_date >= t.ac_date 
             AND i.ac_time >= t.ac_time
       ORDER BY i.ac_date, i.ac_time LIMIT 1) AS exit_date,
       ac_time AS entry_time,       
       (SELECT ac_time
       FROM AC AS i
       WHERE i.ac_event = 0 
             AND i.employee = t.employee 
             AND i.ac_date >= t.ac_date 
             AND i.ac_time >= t.ac_time
       ORDER BY i.ac_date, i.ac_time LIMIT 1) AS exit_time       
FROM AC AS t
WHERE t.ac_date BETWEEN '2018-05-12' AND '2018-05-13' AND t.ac_event = 1;

输出:

employee entry_date  exit_date   entry_time  exit_time
-------------------------------------------------------
2032     2018-05-12  2018-05-12  08:52:00    11:39:33
2032     2018-05-12  2018-05-12  11:48:06    11:52:54
2032     2018-05-12  2018-05-12  11:59:54    12:23:40
2032     2018-05-13  2018-05-13  08:34:43    09:02:25
2032     2018-05-13  2018-05-13  09:12:16    11:45:21
2032     2018-05-13  2018-05-13  12:50:40    12:52:16

那么你可以使用TIMESTAMPDIFF来计算每次进出之间的时间:

SELECT employee, entry_date, exit_date, entry_time, exit_time, 
        SEC_TO_TIME(TIMESTAMPDIFF(SECOND,
                                  CONCAT(entry_date, ' ', entry_time), 
                                  CONCAT(exit_date, ' ', exit_time))) AS duration
FROM (
SELECT employee, ac_date AS entry_date,
       (SELECT ac_date 
       FROM AC AS i
       WHERE i.ac_event = 0 
             AND i.employee = t.employee 
             AND i.ac_date >= t.ac_date 
             AND i.ac_time >= t.ac_time
       ORDER BY i.ac_date, i.ac_time LIMIT 1) AS exit_date,
       ac_time AS entry_time,       
       (SELECT ac_time
       FROM AC AS i
       WHERE i.ac_event = 0 
             AND i.employee = t.employee 
             AND i.ac_date >= t.ac_date 
             AND i.ac_time >= t.ac_time
       ORDER BY i.ac_date, i.ac_time LIMIT 1) AS exit_time       
FROM AC AS t
WHERE t.ac_date BETWEEN '2018-05-12' AND '2018-05-13' AND t.ac_event = 1) AS x

Demo here

【讨论】:

  • 太棒了!他的使用方式是这样的。 sqlfiddle.com/#!9/6f36f/3
  • 虽然答案给出了问题,但我不知道它会对包含数千条记录的表产生什么影响。 2
  • Rahul 的解决方案会为您提供更好的性能,因为他使用的是派生表。请记住,子查询(在选择中)将为初始查询返回的每一行运行该查询。如果您有 1000 行,您将运行 2000 个额外的查询来计算进入和退出时间(听起来并不多,但这显然是一个扩展问题,如果您想保持性能,您需要更聪明地处理索引)
  • @Alex:Rahul 的解决方案将提供更好的性能,但结果也完全不同 :-)
  • @dnoeth 这是一个非常好的观点,我的错 - 我在手机上浏览并看到派生表与子查询。
【解决方案4】:

试试这个: 明智地对每个条目employee, ac_date, ac_time 的数据进行排名并退出单独的集合然后加入,这是一种简单的方法,您可以使用INNER JOIN 而不是LEFT JOIN如果你想要同时进入和退出的记录,性能会更好

SELECT en.employee,  
    en.entry_date,
    ex.exit_date,
    en.entry_time,
    ex.exit_time,
TIMEDIFF(ex.exit_time, en.entry_time) as duration
FROM (
    SELECT employee,
        ac_date AS entry_date,
        ac_time AS entry_time,
        @enRank := @enRank + 1 AS rank
    FROM AC, (SELECT @enRank := 0) r
    WHERE ac_event = 1 ORDER BY employee, ac_date, ac_time) en
LEFT JOIN (
            SELECT employee,
                ac_date AS exit_date,
                ac_time AS exit_time,
                @exRank := @exRank + 1 AS rank
            FROM AC, (SELECT @exRank := 0) r
            WHERE ac_event = 0 ORDER BY employee, ac_date, ac_time) ex ON ex.rank = en.rank
AND ex.employee = en.employee
WHERE en.entry_date >= '2018-05-12' AND ex.exit_date <= '2018-05-13'
ORDER BY en.entry_date

输出 http://sqlfiddle.com/#!9/891d91/18

【讨论】:

    【解决方案5】:

    另一种选择是对同一个表使用双连接以获得退出记录:

    SELECT t1.employee, 
           t1.ac_date AS entry_date,
           t1.ac_time AS entry_time,
           t2.ac_date AS exit_date,
           t2.ac_time AS exit_time,
           SEC_TO_TIME(TIMESTAMPDIFF(SECOND,
                                      CONCAT(t1.ac_date, ' ', t1.ac_time), 
                                      CONCAT(t2.ac_date, ' ', t2.ac_time))) AS duration       
    
    /* fetches entry records */
    FROM AC AS t1       
    
    /* fetches exit records that occur after the correlated entry record */
    INNER JOIN AC AS t2 
    
       ON t1.employee = t2.employee AND t2.ac_event = 0
          AND CONCAT(t1.ac_date, ' ', t1.ac_time) <= CONCAT(t2.ac_date, ' ', t2.ac_time) 
    
    /* fetches exit records that occur between t1, t2 records */
    LEFT JOIN AC AS t3  
       ON t2.employee = t3.employee AND t3.ac_event = 0 
          AND CONCAT(t3.ac_date, ' ', t3.ac_time) >= CONCAT(t1.ac_date, ' ', t1.ac_time) 
          AND CONCAT(t3.ac_date, ' ', t3.ac_time) < CONCAT(t2.ac_date, ' ', t2.ac_time) 
    WHERE t1.ac_date BETWEEN '2018-05-12' AND '2018-05-13' 
          AND t1.ac_event = 1 
          AND t3.employee IS NULL /* There is no record between t1 entry and t2 exit */
    

    Demo here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 2012-10-26
      相关资源
      最近更新 更多