Firebird - вычислить разницу во времени между двумя строками - PullRequest
1 голос
/ 19 сентября 2019

Обзор: У меня есть таблицы SHIFT_LOG, SHIFT_LOG_DET & SHIFT_LOG_ENTRY, имеющие отношения Parent-Child-GrandChild (один-ко-многим).Таким образом, таблица

  • LOG содержит сведения о смене.
  • LOG_DET содержит операторы в конкретном смене, а таблица * LOG_ENTRY
  • регистрирует различные типы записей и временную метку для пользователя всдвиг, как (ДОБАВЛЕНО, НАЧАЛО, ВКЛЮЧЕНО, СОЕДИНЕНО, ЗАВЕРШЕНО).

Проблема: Для данного сдвига я могу получить все операторы и их записи, используя запрос ниже.Что я не могу сделать, так это найти продолжительность, которую оператор потратил на определенный тип записи.т.е. разница между двумя строками ENTRY_TIME.

SELECT 
    ent.ID as ENT_ID,
    det.ID as DET_ID,
    usr.CODE as USR_ID,
    ent.SHIFT_LOG_DET_ID,
    ent.ENTRY_TYPE, 
    IIF(ent.ENTRY_TYPE = 0 , 'ADDED', 
        IIF(ent.ENTRY_TYPE = 1 , 'STARTED', 
        IIF(ent.ENTRY_TYPE = 2 , 'ON-BREAK', 
        IIF(ent.ENTRY_TYPE = 3 , 'JOINED', 
        IIF(ent.ENTRY_TYPE = 4 , 'ENDED', 'UNKNOWN ENTRY'))))) as ENTRY_TYPE_VALUE, 
    ent.ENTRY_TIME+cast('31.12.1899' as timestamp) as ENTRY_TIME
FROM SHIFT_LOG_ENTRY ent
LEFT JOIN SHIFT_LOG_DET det on det.ID = ent.SHIFT_LOG_DET_ID
LEFT JOIN SHIFT_LOG log on log.ID = det.SHIFT_LOG_ID
LEFT JOIN USERS usr on usr.USERID = det.OPERATOR_ID
WHERE log.ID = 1

GROUP BY 
usr.CODE,
ent.SHIFT_LOG_DET_ID,
det.ID,
ent.ID,
ENTRY_TYPE_VALUE,
ent.ENTRY_TIME,
ent.ENTRY_TYPE

Результирующий набор:

enter image description here

Таким образом, Inteval - этовремя, потраченное в секундах на perticular ENTRY_TYPE.т. е.

ROW(1).Interval = ( Row(2).EntryTime - Row(1).EntryTime )

Тип записи ENDED не имеет интервала, поскольку нет другой записи для пользователя после окончания смены.

Версия Firebird 2.5.3

Ответы [ 2 ]

2 голосов
/ 19 сентября 2019

Здесь другой, «активный» подход.Подходит ли он для вашего рабочего процесса, решите сами.Он основан на добавлении специального дополнительного столбца только для того, чтобы связать смежные строки.

Поскольку LOG_ENTRY - это журнал событий, событий из одного источника и событий довольно длинных (15 секунд - много для компьютера), Я бы предположил, что

  1. Данные добавляются только в таблицу, они очень редко или никогда не редактируются и не удаляются
  2. Данные добавляются упорядоченным образом, то есть когда происходит любое событиевставляется - это событие LAST в пакете (в вашем случае пакет, кажется, означает: для данного оператора и данного сдвига).

Если эти предположения верны, я бы добавил одностолбец more (indexed!) таблицы: batch_internal_id.Он будет начинаться с нуля в выбранной вами строке # 1, будет 1 в следующей строке, будет 2 в строке # 3 и т. Д.Он будет сброшен до нуля при изменении пакета (в строке № 8 на снимке экрана).

После этого вычисленное время будет простым непрерывным самосоединением, которое обычно должно быть быстрее, чемс большим количеством подвыборов, по одному на строку.

Примерно так:

SELECT 
    ent.ID as ENT_ID,
    ent.SHIFT_LOG_DET_ID,
    ent.ENTRY_TYPE, 
    DECODE(ent.ENTRY_TYPE, 0 , 'ADDED', 1 , 'STARTED', 2 , 'ON-BREAK', 
          3 , 'JOINED', 4 , 'ENDED', 'UNKNOWN ENTRY') 
       as ENTRY_TYPE_VALUE,  -- better make it an extra table to join!
    ent.ENTRY_TIME+cast('31.12.1899' as timestamp) as ENTRY_TIME,
    ent_next.ENTRY_TIME - ent.ENTRY_TIME as time_elapsed

FROM SHIFT_LOG_ENTRY ent
LEFT JOIN SHIFT_LOG_ENTRY ent_next ON
   (ent.SHIFT_LOG_DET_ID = ent_next.SHIFT_LOG_DET_ID) and 
   (ent.batch_internal_id + 1 = ent_next.batch_internal_id)

ORDER BY ent.SHIFT_LOG_DET_ID, ent.batch_internal_id

Хитрость заключается в том, чтобы обеспечить правильное заполнение batch_internal_id в каждой партии и в то же времяизолированные от других партий.

Вот где вышеприведенные допущения становятся важными.Вы можете легко автоматически заполнить новое внутреннее (относительное к партии) поле идентификатора из триггера SQL , при условии, что вы дали гарантию, что вставляемое событие всегда является последним в пакете.

Примерно так:

CREATE TRIGGER SHIFT_LOG_DET_LINK_EVENTS
  BEFORE UPDATE OR INSERT
  ON SHIFT_LOG_DET
AS
BEGIN
  NEW.batch_internal_id = 0;

  SELECT FIRST(1)  -- we only need one last row per same batch
    prev.batch_internal_id + 1  -- next value
  FROM SHIFT_LOG_DET prev
    WHERE prev.SHIFT_LOG_DET_ID = NEW.SHIFT_LOG_DET_ID -- batch definition
    ORDER BY prev.ENTRY_TIME DESCENDING
    INTO NEW.batch_internal_id;
END

Такой триггер инициализирует относительный идентификатор с нулем при запуске нового пакета и с увеличенным последним идентификатором, если уже были другие строки для пакета.

Однако он критически зависит от того, чтобы всегда вызываться по порядку, когда все предыдущие строки той же партии уже были вставлены, и ни одна из следующих строк еще не вставлена.

Можно также написать команду немного более лаконично, но, возможно,труднее читать.

.......
AS
BEGIN
  NEW.batch_internal_id = 
    COALESCE( (
       SELECT FIRST(1)  -- we only need one last row per same batch
           prev.batch_internal_id + 1  -- next value
       FROM SHIFT_LOG_DET prev
       WHERE prev.SHIFT_LOG_DET_ID = NEW.SHIFT_LOG_DET_ID -- batch definition
       ORDER BY prev.ENTRY_TIME DESCENDING 
    ) , 0);
END
1 голос
/ 19 сентября 2019

Вам нужно будет выбрать следующую дату из соответствующих записей.Вы можете сделать это, используя что-то вроде:

select
  SHIFT_LOG_DET_ID,
  ENTRY_TIME,
  datediff(minute from ENTRY_TIME to NEXT_ENTRY_TIME) as DURATION
from (
  select 
    a.SHIFT_LOG_DET_ID,
    a.ENTRY_TIME, 
    (select min(ENTRY_TIME) 
     from SHIFT_LOG_ENTRY 
     where SHIFT_LOG_DET_ID = a.SHIFT_LOG_DET_ID
     and ENTRY_TIME > a.ENTRY_TIME) as NEXT_ENTRY_TIME
  from SHIFT_LOG_ENTRY a
) b

См. Также fiddle .

В Firebird 3 вы можете использовать оконную функцию LEAD для достижения этой цели:

select
  SHIFT_LOG_DET_ID,
  ENTRY_TIME,
  datediff(minute from ENTRY_TIME 
      to lead(ENTRY_TIME) over (partition by SHIFT_LOG_DET_ID order by ENTRY_TIME)) as DURATION
from SHIFT_LOG_ENTRY

Полное решение

Это решение предоставлено AlphaTry

select
    ENT_ID,
    DET_ID,
    USR_CODE,
    SHIFT_LOG_DET_ID,
    ENTRY_TYPE, 
    ENTRY_TYPE_VALUE,
    ENTRY_TIME,
    datediff(second from ENTRY_TIME to NEXT_ENTRY_TIME) as DURATION
from (
    SELECT 
        ent.ID as ENT_ID,
        det.ID as DET_ID,
        usr.CODE as USR_CODE,
        ent.SHIFT_LOG_DET_ID,
        ent.ENTRY_TYPE as ENTRY_TYPE, 
        case (ent.ENTRY_TYPE)
        when '0' then 'ADDED'
        when '1' then 'STARTED'
        when '2' then 'ON-BREAK'
        when '3' then 'JOINED'
        when '4' then 'ENDED'
        else 'UNKNOWN ENTRY'
        end as ENTRY_TYPE_VALUE,
        ent.ENTRY_TIME+cast('31.12.1899' as timestamp) as ENTRY_TIME, 
        (
            select min(ENTRY_TIME) 
            from SHIFT_LOG_ENTRY 
            where SHIFT_LOG_DET_ID = ent.SHIFT_LOG_DET_ID
            and ENTRY_TIME > ent.ENTRY_TIME

        )+cast('31.12.1899' as timestamp) as NEXT_ENTRY_TIME
    FROM SHIFT_LOG_ENTRY ent
    LEFT JOIN SHIFT_LOG_DET det on det.ID = ent.SHIFT_LOG_DET_ID
    LEFT JOIN SHIFT_LOG log on log.ID = det.SHIFT_LOG_ID
    LEFT JOIN USERS usr on usr.USERID = det.OPERATOR_ID
    WHERE log.ID = 1
    GROUP BY 
    usr.CODE,
    ent.SHIFT_LOG_DET_ID,
    det.ID,
    ent.ID,
    ENTRY_TYPE_VALUE,
    ent.ENTRY_TIME,
    ent.ENTRY_TYPE
) b

Результат

enter image description here

...