Firebird-计算两行之间的时差

时间:2019-09-19 12:52:52

标签: sql firebird firebird2.5

概述:我有SHIFT_LOGSHIFT_LOG_DETSHIFT_LOG_ENTRY表,它们具有Parent-Child-GrandChild关系(一对多)。所以,

  • LOG表包含班次详细信息。
  • LOG_DET包含特定班次中的运算符&
  • LOG_ENTRY表为用户(如ADDED,STARTED,ON-BREAK,JOINED,ENDED)的轮班记录不同的条目类型和时间戳。

问题:对于给定的班次,我可以使用以下查询来获取所有运算符及其条目。我不能做的是找到操作员在特定条目类型上花费的时间。即两行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是在特定ENTRY_TYPE上花费的时间(以秒为单位)。即

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

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

Firebird版本为2.5.3

2 个答案:

答案 0 :(得分:2)

这是一种不同的“主动”方法。是否适合您的工作流程由您自己决定。它是基于添加特殊的额外列而将相邻行链接在一起的。

由于LOG_ENTRY是事件的日志,并且是来自同一来源的事件,并且事件相当长(对于计算机来说15秒是很多时间),因此我认为

  1. 数据仅添加到表中,很少或永远不会被编辑或删除
  2. 数据是按有序方式添加的,即插入任何事件时-这是批处理中的LAST事件(在您的情况下,批处理似乎意味着:对于给定的运算符和给定的班次)。

如果这些假设成立,我将在表中再增加一列(索引!):batch_internal_id。它将在您选择的第1行开始为零,在下一行为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递增。

但是,当所有相同批次的前几行都已插入而下一行还没有插入时,则始终依赖于按顺序调用它。

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

.......
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 :(得分:1)

您需要从相关条目中选择下一个日期。您可以使用类似的方法来做到这一点:

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

结果

enter image description here