Рассчитать две записи и объединить их в одной строке MySQL - PullRequest
0 голосов
/ 14 мая 2018

У меня есть таблица, которая содержит данные контроля доступа сотрудников, входа и выхода из офиса в разных строках.Вывод основан на запросе в примере диапазона дат и времени 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 - это выход.

Мне нужно сделатьзапрос, который показывает данные в той же строке (вход / выход), чтобы затем определить время, в течение которого сотрудник находится вне офиса.Ej:

Вывод для первого блока:

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

Ответы [ 5 ]

0 голосов
/ 14 мая 2018

Другой вариант - использовать двойное соединение к одной и той же таблице, чтобы получить выходную запись:

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 */

Демо здесь

0 голосов
/ 14 мая 2018

Это упрощенная версия вашего исходного запроса:

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
;

См. скрипка

Но независимо от того, как вы это пишете, это всегда своего рода неравное объединение (т.е. не основанное на =), и производительность может быть плохой без соответствующих индексов.

Кстати, если бы это был MariaDB или MySQL 8, это была бы простая задача для LAG / LEAD.

0 голосов
/ 14 мая 2018

Вы можете использовать коррелированные подзапросы для получения пар входа / выхода:

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

Демо здесь

0 голосов
/ 14 мая 2018

ПОПРОБУЙТЕ ЭТО: Просто оцените данные для каждой записи 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

0 голосов
/ 14 мая 2018

Попробуйте это:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...