Firebird - 计算两行之间的时间差
Firebird - Calculate time difference between two rows
概述:我有tableSHIFT_LOG
、SHIFT_LOG_DET
和SHIFT_LOG_ENTRY
有亲子孙关系(一对多)。所以,
- LOG table 包含轮班详细信息。
- LOG_DET 包含特定班次的运算符 &
- LOG_ENTRY table 记录轮班中用户的不同条目类型和时间戳,例如(添加、开始、休息、加入、结束)。
问题: 对于给定的班次,我可以使用以下查询获取所有操作员及其条目。我不能做的是找到操作员在特定条目类型上花费的持续时间。即两行之间的差异 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 秒对计算机来说很长),我假设
- 数据仅添加到 table,很少或从未被编辑或删除
- 数据按顺序添加,即插入任何事件时 - 它是批处理中的最后一个事件(在您的情况下,批处理似乎意味着:对于给定的运算符和给定的班次)。
如果这些假设成立,我将在 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
概述:我有tableSHIFT_LOG
、SHIFT_LOG_DET
和SHIFT_LOG_ENTRY
有亲子孙关系(一对多)。所以,
- LOG table 包含轮班详细信息。
- LOG_DET 包含特定班次的运算符 &
- LOG_ENTRY table 记录轮班中用户的不同条目类型和时间戳,例如(添加、开始、休息、加入、结束)。
问题: 对于给定的班次,我可以使用以下查询获取所有操作员及其条目。我不能做的是找到操作员在特定条目类型上花费的持续时间。即两行之间的差异 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 秒对计算机来说很长),我假设
- 数据仅添加到 table,很少或从未被编辑或删除
- 数据按顺序添加,即插入任何事件时 - 它是批处理中的最后一个事件(在您的情况下,批处理似乎意味着:对于给定的运算符和给定的班次)。
如果这些假设成立,我将在 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