Записи здания входа и выхода - PullRequest
0 голосов
/ 26 мая 2018

Из следующей таблицы:

timestamp         inout  Name
2018-04-01 14:00    0    Tom
2018-04-02 06:00    1    Tom
2018-04-02 14:00    0    Tom
2018-04-03 06:00    1    Tom
2018-04-01 22:00    0    Rob
2018-04-02 14:00    1    Rob
2018-04-02 22:00    0    Rob
2018-04-03 13:00    1    Rob
2018-04-01 12:55    0    John
2018-04-02 06:05    1    John
2018-04-03 06:10    1    John
2018-04-01 14:05    0    Anna
2018-04-02 14:10    0    Anna
2018-04-02 14:15    1    Anna
2018-04-02 14:20    0    Anna
2018-04-03 14:05    0    Anna
2018-04-01 22:00    1    Mary
2018-04-02 06:00    0    Mary
2018-04-02 22:00    1    Mary
2018-04-03 06:00    0    Mary

, где 1 = in 0 = out Мне нужно собрать данные "записей входа и выхода за 2018-04-02" и представить их в виде таблицы:

d1-in-timestamp   d0-out-timestamp  Name
2018-04-02 07:00  2018-04-02 15:00  Tom
2018-04-02 14:00  2018-04-02 22:00  Rob
2018-04-02 06:05  -                 John
-                 2018-04-02 14:10  Anna
2018-04-02 14:15  2018-04-02 14:20  Anna
2018-04-02 00:00  2018-04-02 06:00  Mary
2018-04-02 22:00  2018-04-02 00:00  Mary

В идеальном мире Том входит один раз в здание, минуя «ДВЕРЬ» и один раз выходит через «ДВЕРЬ».Том идеален!Будь как Том!:)Роб тоже идеален, но он сонный, поэтому он приходит на дневную смену.:ПАнна приходит на работу с Томом.Том держит «Дверь» открытой для нее, поэтому нет никаких записей о ее входе.Более того, она продолжает возвращаться, потому что она что-то забыла.Джон дурак!Он приходит на работу допоздна, поэтому он должен решить это, но он всегда проскальзывает с кем-то еще через «ДВЕРЬ», когда другой человек уходит.И наконец жениться.Она в ночной смене, поэтому ее нужно видеть в таблице, где две записи делятся за день. Можно ли получить такой результат в одной таблице одним SQL-запросом? Пока что я управляю SQL-запросом так:

select timestamp as d1, (select timestamp from DOOR where timestamp>m1.timestamp and inout=0 and name=m1.name) as d0, name from DOOR as m1 where substring(timestamp,1,10)='2018-04-02' and inout=1 order by name, timestamp 

Запрос работает для людей из "идеального мира" (Том и Роб) и более / менее для Джона.К сожалению, запрос не работает для Анны и Мэри.

PS: Извините за мой английский

Ответы [ 4 ]

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

Мэри и другие ночные смены

Сначала поговорим о Мэри и других ночных сменщиках.Мы можем найти их в штампах, когда отфильтровываем все в штампах, где:

  • В тот же день не существует ни одного выходного штампа, то есть после штампа.
  • Но выходной штампна следующий день существует, и нет штампа на следующий день, который существует до этого штампа.

Для штампа мы используем 00:00 часов следующего дня штампа..

SELECT d.`timestamp` `timestamp_in`,
       timestampadd(second, -1 * second(d.`timestamp`), timestampadd(minute, -1 * minute(d.`timestamp`), timestampadd(hour, -1 * hour(d.`timestamp`), timestampadd(day, 1, (d.`timestamp`))))) `timestamp_out`,
       d.`name`
       FROM `door` d
       WHERE d.`inout` = 1
             AND NOT EXISTS (SELECT *
                                    FROM `door` di
                                    WHERE di.`name` = d.`name`
                                          AND di.`inout` = 0
                                          AND di.`timestamp` > d.`timestamp`
                                          AND date(di.`timestamp`) = date(d.`timestamp`))
             AND EXISTS (SELECT *
                                FROM `door` di
                                WHERE di.`name` = d.`name`
                                      AND di.`inout` = 0
                                      AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                      AND NOT EXISTS (SELECT *
                                                             FROM `door` dii
                                                             WHERE dii.`name` = di.`name`
                                                                   AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                   AND dii.`timestamp` < di.`timestamp`
                                                                   AND dii.`inout` = 1))

Чтобы получить штампы ночных смен, мы можем использовать ту же логику, но все переключаем:

  • > становится < в сравнениях дати наоборот.
  • Если для штампа есть 0, укажите 1 для штампа и наоборот.
  • Вместо следующего дня сравните спредыдущий день.

Для штампа «in» мы используем 00:00 часов дня штампа «out».

Если мы UNION ALL оба запроса, мы получаем часть дляночные смены.

остальная часть банды, также известные как смены дня

Получение штампов на деньсейчас легко.Нам просто нужно отменить условие, которое мы использовали, чтобы найти ночных сменщиков.Чтобы получить входящие штампы:

SELECT d.`timestamp`,
       d.`name`
       FROM `door` d
       WHERE d.`inout` = 1
             AND (EXISTS (SELECT *
                                 FROM `door` di
                                 WHERE di.`name` = d.`name`
                                       AND di.`inout` = 0
                                       AND di.`timestamp` > d.`timestamp`
                                       AND date(di.`timestamp`) = date(d.`timestamp`))
                   OR NOT EXISTS (SELECT *
                                         FROM `door` di
                                         WHERE di.`name` = d.`name`
                                               AND di.`inout` = 0
                                               AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                               AND NOT EXISTS (SELECT *
                                                                      FROM `door` dii
                                                                      WHERE dii.`name` = di.`name`
                                                                            AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                            AND dii.`timestamp` < di.`timestamp`
                                                                            AND dii.`inout` = 1)))

Для выходных штампов мы снова можем поменять местами, > становится < и т. Д., Как указано выше.

Конечно, здесь мыне может генерировать входящие штампы для выходных штампов и наоборот.Мы должны были бы FULL OUTER JOIN печатать на штампах и на штампах дневных оборотней.Но MySQL по крайней мере в более низких версиях не поддерживает эту операцию.Таким образом, мы UNION делаем два LEFT JOIN, меняя роли во втором LEFT JOIN по сравнению с первым.

Собираем все вместе

Теперьмы можем просто UNION ALL ночные и дневные смены, чтобы получить полный результат за все дни.И мы можем SELECT FROM, чтобы получить результат за определенный день:

SELECT coalesce(x.`timestamp_in`, '-') `d1-in-timestamp`,
       coalesce(x.`timestamp_out`, '-') `d0-out-timestamp`,
       x.`name`
       FROM (SELECT r.`timestamp` `timestamp_in`,
                    s.`timestamp` `timestamp_out`,
                    r.`name`
                    FROM (SELECT d.`timestamp`,
                                 d.`name`
                                 FROM `door` d
                                 WHERE d.`inout` = 1
                                       AND (EXISTS (SELECT *
                                                           FROM `door` di
                                                           WHERE di.`name` = d.`name`
                                                                 AND di.`inout` = 0
                                                                 AND di.`timestamp` > d.`timestamp`
                                                                 AND date(di.`timestamp`) = date(d.`timestamp`))
                                             OR NOT EXISTS (SELECT *
                                                                   FROM `door` di
                                                                   WHERE di.`name` = d.`name`
                                                                         AND di.`inout` = 0
                                                                         AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                         AND NOT EXISTS (SELECT *
                                                                                                FROM `door` dii
                                                                                                WHERE dii.`name` = di.`name`
                                                                                                      AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                                      AND dii.`timestamp` < di.`timestamp`
                                                                                                      AND dii.`inout` = 1)))) r
                         LEFT JOIN (SELECT d.`timestamp`,
                                           d.`name`
                                           FROM `door` d
                                           WHERE d.`inout` = 0
                                                 AND (EXISTS (SELECT *
                                                                     FROM `door` di
                                                                     WHERE di.`name` = d.`name`
                                                                           AND di.`inout` = 1
                                                                           AND di.`timestamp` < d.`timestamp`
                                                                           AND date(di.`timestamp`) = date(d.`timestamp`))
                                                       OR NOT EXISTS (SELECT *
                                                                             FROM `door` di
                                                                             WHERE di.`name` = d.`name`
                                                                                   AND di.`inout` = 1
                                                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                   AND NOT EXISTS (SELECT *
                                                                                                          FROM `door` dii
                                                                                                          WHERE dii.`name` = di.`name`
                                                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                                                AND dii.`timestamp` > di.`timestamp`
                                                                                                                AND dii.`inout` = 0)))) s
                                   ON s.`name` = r.`name`
                                      AND date(s.`timestamp`) = date(r.`timestamp`)
                                      AND s.`timestamp` > r.`timestamp`
             UNION
             SELECT u.`timestamp` `timestamp_in`,
                    t.`timestamp` `timestamp_out`,
                    t.`name`
                    FROM (SELECT d.`timestamp`,
                                 d.`name`
                                 FROM `door` d
                                 WHERE d.`inout` = 0
                                       AND (EXISTS (SELECT *
                                                           FROM `door` di
                                                           WHERE di.`name` = d.`name`
                                                                 AND di.`inout` = 1
                                                                 AND di.`timestamp` < d.`timestamp`
                                                                 AND date(di.`timestamp`) = date(d.`timestamp`))
                                             OR NOT EXISTS (SELECT *
                                                                   FROM `door` di
                                                                   WHERE di.`name` = d.`name`
                                                                         AND di.`inout` = 1
                                                                         AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                         AND NOT EXISTS (SELECT *
                                                                                                FROM `door` dii
                                                                                                WHERE dii.`name` = di.`name`
                                                                                                      AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                                      AND dii.`timestamp` > di.`timestamp`
                                                                                                      AND dii.`inout` = 0)))) t
                         LEFT JOIN (SELECT d.`timestamp`,
                                           d.`name`
                                           FROM `door` d
                                           WHERE d.`inout` = 1
                                                 AND (EXISTS (SELECT *
                                                                     FROM `door` di
                                                                     WHERE di.`name` = d.`name`
                                                                           AND di.`inout` = 0
                                                                           AND di.`timestamp` > d.`timestamp`
                                                                           AND date(di.`timestamp`) = date(d.`timestamp`))
                                                       OR NOT EXISTS (SELECT *
                                                                             FROM `door` di
                                                                             WHERE di.`name` = d.`name`
                                                                                   AND di.`inout` = 0
                                                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                   AND NOT EXISTS (SELECT *
                                                                                                          FROM `door` dii
                                                                                                          WHERE dii.`name` = di.`name`
                                                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                                                AND dii.`timestamp` < di.`timestamp`
                                                                                                                AND dii.`inout` = 1)))) u
                                   ON u.`name` = t.`name`
                                      AND date(u.`timestamp`) = date(t.`timestamp`)
                                      AND u.`timestamp` < t.`timestamp`
             UNION
             SELECT d.`timestamp` `timestamp_in`,
                    timestampadd(second, -1 * second(d.`timestamp`), timestampadd(minute, -1 * minute(d.`timestamp`), timestampadd(hour, -1 * hour(d.`timestamp`), timestampadd(day, 1, (d.`timestamp`))))) `timestamp_out`,
                    d.`name`
                    FROM `door` d
                    WHERE d.`inout` = 1
                          AND NOT EXISTS (SELECT *
                                                 FROM `door` di
                                                 WHERE di.`name` = d.`name`
                                                       AND di.`inout` = 0
                                                       AND di.`timestamp` > d.`timestamp`
                                                       AND date(di.`timestamp`) = date(d.`timestamp`))
                          AND EXISTS (SELECT *
                                             FROM `door` di
                                             WHERE di.`name` = d.`name`
                                                   AND di.`inout` = 0
                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                   AND NOT EXISTS (SELECT *
                                                                          FROM `door` dii
                                                                          WHERE dii.`name` = di.`name`
                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                AND dii.`timestamp` < di.`timestamp`
                                                                                AND dii.`inout` = 1))
             UNION ALL
             SELECT timestampadd(second, -1 * second(d.`timestamp`), timestampadd(minute, -1 * minute(d.`timestamp`), timestampadd(hour, -1 * hour(d.`timestamp`), d.`timestamp`))) `timestamp_in`,
                    d.`timestamp` `timestamp_out`,
                    d.`name`
                    FROM `door` d
                    WHERE d.`inout` = 0
                          AND NOT EXISTS (SELECT *
                                                 FROM `door` di
                                                 WHERE di.`name` = d.`name`
                                                       AND di.`inout` = 1
                                                       AND di.`timestamp` < d.`timestamp`
                                                       AND date(di.`timestamp`) = date(d.`timestamp`))
                          AND EXISTS (SELECT *
                                             FROM `door` di
                                             WHERE di.`name` = d.`name`
                                                   AND di.`inout` = 1
                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                   AND NOT EXISTS (SELECT *
                                                                          FROM `door` dii
                                                                          WHERE dii.`name` = di.`name`
                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                AND dii.`timestamp` > di.`timestamp`
                                                                                AND dii.`inout` = 0))) x
       WHERE date(x.`timestamp_in`) = '2018-04-02'
              OR date(x.`timestamp_out`) = '2018-04-02'
       ORDER BY x.`name`,
                x.`timestamp_in`;

db <> fiddle

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

Всего одно простое дополнение: добавьте строку для Марии с максимальным временем (2018-04-02 23:59) http://sqlfiddle.com/#!9/bb41b1/6
И тогда вы можете использовать следующую логику:

  select a.name,
    case when a.name != 'Mary' and (b.out_time is null or a.in_time <= b.out_time) then a.in_time
         when a.name != 'Mary' and b.out_time is not null and a.in_time > b.out_time then null
         when a.name = 'Mary' and  a.in_time > b.out_time then '2018-04-02 00:00:00'
         else a.in_time
    end as in_time,
    b.out_time     
    from
    (select name,time1 as in_time  from have1
    where inout1 = 1 and (substring(time1,1,10)='2018-04-02')) a
    left join
    (select name,time1 as out_time  from have1
    where inout1 = 0 and (substring(time1,1,10)='2018-04-02')) b
    on a.name = b.name   

Вывод:

name    in_time                 out_time
Tom     2018-04-02 06:00:00  2018-04-02 14:00:00
Rob     2018-04-02 14:00:00  2018-04-02 22:00:00
John    2018-04-02 06:05:00  (null)
Anna    (null)               2018-04-02 14:10:00
Anna    2018-04-02 14:15:00  2018-04-02 14:20:00
Mary    2018-04-02 00:00:00  2018-04-02 06:00:00
Mary    2018-04-02 22:00:00  2018-04-02 23:59:59

Дайте мне знать, если потребуется какое-либо разъяснение.

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

Это сложный вопрос, но я нашел для этого запрос.Он использует несколько объединений, подзапросов и объединений, но создает желаемый результат.Я начал с @RajatJaiswals fiddle, но создал совершенно новый запрос.

SELECT * FROM (
  SELECT
    IF(inA.timestamp < '2018-04-02', '2018-04-02 00:00:00', inA.timestamp) AS `d1-in-timestamp`, 
    IFNULL(IF(outA.timestamp > '2018-04-02 23:59:59', CAST(DATE_ADD('2018-04-02', INTERVAL 1 DAY) AS DATETIME), outA.timestamp), '-') AS `d0-out-timestamp`, 
    inA.name AS `Name`
  FROM 
    attendance AS inA
    LEFT JOIN attendance AS outA ON (
      inA.name = outA.name 
      AND outA.inout = 0
      AND inA.timestamp < outA.timestamp
      AND NOT EXISTS(
        SELECT betweenA.name 
        FROM attendance AS betweenA
        WHERE 
          betweenA.name = inA.name
          AND betweenA.timestamp > inA.timestamp
          AND betweenA.timestamp < outA.timestamp
      )
    )
  WHERE 
    inA.inout = 1 
    AND (
      CAST(inA.timestamp AS DATE) = '2018-04-02' 
      OR CAST(outA.timestamp AS DATE) = '2018-04-02'
    )

  UNION

  SELECT 
    '-' AS `d1-in-timestamp`, 
    IF(outA.timestamp > '2018-04-02 23:59:59', CAST(DATE_ADD('2018-04-02', INTERVAL 1 DAY) AS DATETIME), outA.timestamp) AS `d0-out-timestamp`, 
    outA.name AS `Name`
  FROM
    attendance AS outA
    LEFT JOIN attendance AS inA ON (
      inA.name = outA.name 
      AND inA.inout = 1
      AND inA.timestamp < outA.timestamp
      AND NOT EXISTS(
        SELECT betweenA.name 
        FROM attendance AS betweenA
        WHERE 
          betweenA.name = inA.name
          AND betweenA.timestamp > inA.timestamp
          AND betweenA.timestamp < outA.timestamp
      )
    )
  WHERE 
    outA.inout = 0
    AND  CAST(outA.timestamp AS DATE) = '2018-04-02'
    AND inA.name IS NULL
) AS a
ORDER BY `Name`, `d1-in-timestamp`

Это сложный запрос, который на первый взгляд может показаться сложным, но я пытаюсь разбить его на небольшие части, чтобы объяснить, что он делает:

Внешний SELECT предназначен только для сортировки по полному результату.Это необходимо из-за оператора UNION.

  • Первое внутреннее предложение SELECT просто обрабатывает некоторые выходные преобразования
    • IF(inA.timestamp < '2018-04-02', '2018-04-02 00:00:00', inA.timestamp) только для форматирования и заменяет временные метки изпозавчера с 00:00 дня, о котором идет речь
    • IFNULL(IF(outA.timestamp > '2018-04-02 23:59:59', CAST(DATE_ADD('2018-04-02', INTERVAL 1 DAY) AS DATETIME), outA.timestamp), '-') снова для форматирования, но выполняет две функции: замените ноль на -, если человек не вышел из здания, и замените временные меткина следующий день с 00:00 дня после
  • В предложении FROM я использую JOIN для объединения записей о входе в здание (inA)с записями о выходе из здания (outA).Интересной частью является предложение ON:

    • Я использую столбец name для объединения записей только одного человека
    • outA Таблица должна смотреть только на людей, покидающих(inout = 0)
    • inA.timestamp < outA.timestamp если человек не вошел до того, как покинуть эти две записи, не следует объединять
    • Не должно быть записей активности междудве записи объединяются.Это обрабатывается подзапросом NOT EXISTS.Он ищет любую запись, которая

      • принадлежит одному и тому же лицу (betweenA.name = inA.name)
      • , произошедшая после inA рассматриваемой записи
      • , произошедшей до того, как outA запись в вопросе

      Если такая запись существует, предложение NOT EXISTS оценивается как ложное и записи не объединяются.Таким образом, только последующие входящие и исходящие записи объединяются.

  • Предложение WHERE простое:
    • Убедитесь, что выбраны только лица, входящие в зданиес inA
    • Как минимум одна из записей должна быть от нужной даты (CAST(inA.timestamp AS DATE) преобразует метку времени в дату, таким образом удаляя часть времени и упрощая сравнение)

Выбирает все записи, в которых был зарегистрирован человек, входящий в здание.Сейчас мы все еще скучаем по делу Анны, которая не была зарегистрирована, входя в зданиеВот где UNION вступает в действие и добавляет эту информацию к результату.

  • Снова SELECT - это просто логика вывода:
    • У нас нет записи о человекевходя, следовательно, никогда не будет отметки времени.Просто верните '-' для времени входа
    • Логика для обработки оставленной временной метки такая же, как указано выше
  • На этот раз мы начинаем с оставления записей и присоединяем входящие записи ких.Предложение ON делает следующее:
    • Используйте name для объединения только записей одного человека
    • outA.inout = 1, поскольку в таблице объединения должны использоваться только записи ввода
    • запись о входе должна произойти до того, как здание будет покинуто (inA.timestamp < outA.timestamp)
    • Как указано выше, между
  • в предложении WHERE не может быть никаких других записейснова делает некоторые важные ограничения:
    • outA.inout = 0, поскольку нам нужна таблица, ограниченная записями о выходе из здания
    • CAST(outA.timestamp AS DATE) = '2018-04-02' на этот раз только с проверкой даты outA, так как нетзапись о входе.
    • Используйте результаты только в тех случаях, когда запись о входе не найдена (т. е. там, где не было найдено записи для присоединения).Это тот случай, если inA.name IS NULL

Последнее - это предложение ORDER BY, которое самоочевидно.

Вывод:

|     d1-in-timestamp |    d0-out-timestamp | Name |
|---------------------|---------------------|------|
|                   - | 2018-04-02 14:10:00 | Anna |
| 2018-04-02 14:15:00 | 2018-04-02 14:20:00 | Anna |
| 2018-04-02 06:05:00 |                   - | John |
| 2018-04-02 00:00:00 | 2018-04-02 06:00:00 | Mary |
| 2018-04-02 22:00:00 | 2018-04-03 00:00:00 | Mary |
| 2018-04-02 14:00:00 | 2018-04-02 22:00:00 |  Rob |
| 2018-04-02 06:00:00 | 2018-04-02 14:00:00 |  Tom |

Вы можете попробовать это в следующей скрипте SQL: http://sqlfiddle.com/#!9/e618bb/7/0

0 голосов
/ 26 мая 2018
SELECT t.Name,t1.TimeStamp asintime,t.timestamp as outtime
FROM tmpAttendance t
LEFT OUTER JOIN tmpAttendance t1 ON t.Name = t1.Name 
                 AND t1.inout = 1
                 AND CAST(t.timestamp AS DATE) =  CAST(t1.timestamp AS DATE)
 WHERE t.inout = 0
 ORDER BY t.Name ,t1.TimeStamp

http://sqlfiddle.com/#!9/df678/5

...