Firebird - 计算两行之间的时间差

Firebird - Calculate time difference between two rows

概述:我有tableSHIFT_LOGSHIFT_LOG_DETSHIFT_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

结果集:

所以 Inteval 是在特定 ENTRY_TYPE 上花费的时间(以秒为单位)。即

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

条目类型 ENDED 没有间隔,因为轮班结束后用户没有其他条目。

Firebird 版本为 2.5.3

您需要select相关条目中的下一个日期。你可以使用类似的东西来做到这一点:

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 中,您可以使用 window function 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

结果

这是一种不同的 "pro-active" 方法。它是否适合您的工作流程由您自己决定。它基于将特殊的额外列添加到 link 相邻行中。

因为 LOG_ENTRY 是一个事件日志,而且事件来自同一来源,而且事件相当长(15 秒对计算机来说很长),我假设

  1. 数据仅添加到 table,很少或从未被编辑或删除
  2. 数据按顺序添加,即插入任何事件时 - 它是批处理中的最后一个事件(在您的情况下,批处理似乎意味着:对于给定的运算符和给定的班次)。

如果这些假设成立,我将在 table 中再添加一列(索引!):batch_internal_id。它将在您选择的第 1 行开始为 0,在下一行为 1,在第 3 行为 2,依此类推。当批次更改时(在屏幕截图的第 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 trigger 自动填充新的内部(批次相关)ID 字段,前提是您做出保证,插入的事件始终在批次中最后。

像这样:

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

这样的触发器会在新批次开始时将相对 ID 初始化为零,如果该批次已经有其他行,则使用递增的最后一个 ID。

然而,当同一批的所有前一行都已插入且下一行的 none 已插入时,它的关键依赖于始终按顺序调用。

也可以将命令写得更简洁一些,但可能更难阅读。

.......
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