t-sql汇总时间戳之间的差异

时间:2009-10-22 18:21:14

标签: sql sql-server tsql

我正在跟踪机器状态,可以是0,1和2, 并使用time_stamp将该数据存储在sql表中。 我在sql server中有下一个字段的表: ID(INT) TIME_STAMP(日期时间) MACHINE_STATE(INT)

机器状态与机器状态有关:
machine_state = 0 -machine stooped
machine_state = 1台带报警器的机器 machine_state = 2台机器运行

现在我想计算每个班次中每个州的机器有多长。 转移是

  1. 8:00-17:00
  2. 17:00-01:00
  3. 01:00-08:00
  4. 我的问题是如何计算每个机器状态的时间(sum_time_0,sum_time_1,sum_time_2)并按班次分组。我想以秒为单位计算时间,然后转换为分钟。

    为了获得更好的图片,我确实导出了表格的一部分

    EXPORT_TABLE
    id    time_stamp          machine_state
    1623  6.10.2009 17:09:00  1
    1624  6.10.2009 17:17:00  2
    1625  6.10.2009 17:17:00  1
    1626  6.10.2009 17:17:00  2
    1627  6.10.2009 17:18:00  1
    1628  6.10.2009 17:18:00  2
    1629  6.10.2009 18:04:00  1
    1630  6.10.2009 18:06:00  2
    1631  6.10.2009 18:07:00  1
    1632  6.10.2009 18:12:00  2
    1633  6.10.2009 18:28:00  1
    1634  6.10.2009 18:28:00  2
    1635  6.10.2009 19:16:00  1
    1636  6.10.2009 19:21:00  2
    1637  6.10.2009 19:49:00  1
    1638  6.10.2009 20:23:00  2
    

    任何建议都会有所帮助。 提前谢谢。

7 个答案:

答案 0 :(得分:2)

您可以为每一行加入下一个机器状态,然后按州分组,并对时间差进行求和......

create table #t(id int identity(1,1), ts datetime, ms tinyint);

insert into #t
select '6.10.2009 17:09:00',  1
union select '6.10.2009 17:17:00',  2
union select '6.10.2009 17:17:00',  1
union select '6.10.2009 17:17:00',  2
union select '6.10.2009 17:18:00',  1
union select '6.10.2009 17:18:00',  2
union select '6.10.2009 18:04:00',  1
union select '6.10.2009 18:06:00',  2
union select '6.10.2009 18:07:00',  1
union select '6.10.2009 18:12:00',  2
union select '6.10.2009 18:28:00',  1
union select '6.10.2009 18:28:00',  2
union select '6.10.2009 19:16:00',  1
union select '6.10.2009 19:21:00',  2
union select '6.10.2009 19:49:00',  1
union select '6.10.2009 20:23:00',  2

select
    t.ms,
    sum(datediff(mi, t.ts, tn.ts)) as total_mintues
from
    #t t
    inner join #t tn on
        tn.id = (select top 1 t2.id 
                from #t t2 
                where t2.id > t.id and t2.ms <> t.ms
                order by t2.id)
group by
    t.ms

/*
ms  total_mintues
1   54
2   140
*/

drop table #t

答案 1 :(得分:1)

这是我如何做的概述。我做了一些假设,这些假设可能无效或不适用于你的情况,所以我没有把所有的东西编码。

首先,我将问题分解为块:一次计算一个班次的数据。 (我猜你每天运行一次,或者每周运行一次。)

我会将其实现为具有两个参数的存储过程:

  • @ShiftDate,指定要计算的日期(仅使用日期部分,忽略任何时间值)
  • @Shift,指定要分析的班次(1,2,3,如您所定义)

建立两个“完整”日期时间,一个用于班次开始,一个用于结束。例如,如果@ShiftDate ='2009年10月22日'和@Shift = 2,你就得到了

  • @ShiftStart ='Oct 22,2009 17:00:00'
  • @ShiftStop ='2009年10月23日1:00:00'

创建临时表以保存我们将要分析的数据子集。像这样填充它:

  • 复制@ShiftStart和@ShiftStop之间的所有数据
  • 请勿包含连续(按时间)条目具有相同状态的任何数据。如果存在任何此类数据,则丢弃除最早条目之外的所有数据。 (看起来你的数据是以这种方式生成的 - 但是你想假设数据总是好的吗?)
  • 为均匀递增的计数器(1,2,3等)添加一列。看起来你已经有了这个,但是再一次,你想确定在这里。

接下来,检查@ShiftStart和@ShiftStop是否存在条目。如果没有这样的条目:

  • 对于@ShiftStart,创建条目并将machine_state设置为@ShiftStart
  • 之前最新条目的值。
  • 对于@ShiftStop,创建条目并将machine_state设置为,以及任何内容,因为我们不会引用该值
  • 在这两种情况下,请确保正确配置计数器列(@ ShiftStart的计数器比最早的值小1,@ SpeedStops'计数器比最后一个值大1)
  • (以上就是为什么你把它作为临时表。如果你不能加载这些虚拟行,你将不得不使用过程代码来遍历表,这是一种程序代码,使数据库陷入困境服务器。)

您需要这些条目来获取班次开始与该班次中第一个记录条目之间的时间数据,并且在班次结束时也是如此。

此时,项目按时间排序,具有均匀递增的计数器列(1,2,3)。假设以上所有,以下查询  应该返回您正在寻找的数据:

SELECT
   et.machine_state
  ,sum(datediff(ss, et.time_stamp, thru.time_stamp))      TotalSeconds
  ,sum(datediff(ss, et.time_stamp, thru.time_stamp)) / 60 TotalMinutes
 from #EXPORT_TABLE et
  inner join #EXPORT_TABLE thru
   on thru.id = et.id + 1
 group by et.machine_state
 order by et.machine_state

注意:

  • 这是为MS SQL Server编写的。您的语言语法可能有所不同。
  • 我没有测试过这段代码。故意包含任何拼写错误,以便您的最终版本优于我的。
  • EXPORT_TABLE是上述临时表。

  • 在MS SQL中,将整数之和除以整数将产生截断的整数,这意味着59秒将变为0分钟。 如果您需要更高的准确度,除以60.0将产生十进制值。

这只是一个框架。我认为你可以将它扩展到你必须处理的任何条件。

答案 2 :(得分:1)

您可以使用独占联接查找上一行:

select
    State = prev.ms,
    MinutesInState = sum(datediff(mi, prev.ts, cur.ts))
from @t cur
inner join @t prev
    on prev.id < cur.id
left join @t inbetween
    on prev.id < inbetween.id
    and inbetween.id < cur.id
where inbetween.id is null
group by prev.ms

然后查询按机器状态分组。结果与其他答案不同,我很好奇哪一个是对的!

State  MinutesInState
1      54
2      140

以下是我使用的示例数据:

declare @t table (id int identity(1,1), ts datetime, ms tinyint);

insert into @t
select '6.10.2009 17:09:00',  1
union select '6.10.2009 17:17:00',  2
union select '6.10.2009 17:17:00',  1
union select '6.10.2009 17:17:00',  2
union select '6.10.2009 17:18:00',  1
union select '6.10.2009 17:18:00',  2
union select '6.10.2009 18:04:00',  1
union select '6.10.2009 18:06:00',  2
union select '6.10.2009 18:07:00',  1
union select '6.10.2009 18:12:00',  2
union select '6.10.2009 18:28:00',  1
union select '6.10.2009 18:28:00',  2
union select '6.10.2009 19:16:00',  1
union select '6.10.2009 19:21:00',  2
union select '6.10.2009 19:49:00',  1
union select '6.10.2009 20:23:00',  2

答案 3 :(得分:1)

如果你只想要快速和肮脏,这样做:

select curr.*, prev.*
from EXPORT_TABLE curr
outer apply (
  select top 1 * from EXPORT_TABLE prev
  where curr.time_stamp > prev.time_stamp
  order by time_stamp desc, id desc
  ) prev

从那里开始。

但是这个方法,以及这个涉及非等值连接的页面上的一些类似方法,不能很好地扩展。要处理大量数据,我们必须使用不同的技术。

您的ID显示为顺序。是吗?这可能很有用。如果没有,我们应该创建一个。

if object_id('tempdb..#pass1') is not null drop table #pass1
create table #pass1 (
  id            int
, time_stamp    smalldatetime
, machine_state tinyint
, seqno         int primary key -- this is important
)

insert #pass1 
select 
  id
, time_stamp
, machine_state
, seqno = row_number() over (order by time_stamp, id)
from EXPORT_TABLE

一旦我们有序列ID,我们就可以等同于它:

if object_id('tempdb..#pass2') is not null drop table #pass2
create table #pass2 (
  id              int
, time_stamp      smalldatetime
, machine_state   tinyint
, seqno           int primary key
, time_stamp_prev smalldatetime
)
insert #pass2
select 
  id
, time_stamp
, machine_state
, seqno
, time_stamp_prev = b.time_stamp
from #pass1 a
left join #pass1 b on a.seqno = b.seqno + 1

从这里开始,您的查询应该只是写自己。但要注意与班次重叠的机器状态。

这种方法虽然看起来很昂贵,但随着音量的增加会很好。您订购一次数据,然后加入一次。如果id是顺序的,你可以跳过第一步,确保id上有一个聚簇主键,并加入id而不是seqno。

如果你有真正大量数据,那么你可以这样做:

if object_id('tempdb..#export_table') is not null drop table #export_table
create table #pass1 (
  id              int
, time_stamp      smalldatetime
, machine_state   tinyint
, seqno           int primary key -- ensures proper ordering for the UPDATE
, time_stamp_prev smalldatetime
)

insert #export_table (
  id
, time_stamp
, machine_state
, seqno
)    
select 
  id
, time_stamp
, machine_state
, seqno = row_number() over (order by time_stamp, id)
from EXPORT_TABLE

-- do some magic
declare @time_stamp smalldatetime
update #export_table set
  time_stamp_prev = @time_stamp
, @time_stamp = time_stamp

这将胜过所有其他方法。如果你的id是正确的顺序(它不必是顺序的,只是按照正确的顺序),你可以跳过第一步并在id上定义一个聚集索引,如果它还没有在那里。

答案 4 :(得分:0)

你可以这样做:

select t1.time_stamp time_start, t2.time_stamp time_finish, t1.machine_state
from EXPORT_TABLE t1, EXPORT_TABLE t2
where t2.time_stamp = (select min(time_stamp) from @table where time_stamp > t1.time_stamp)

这将返回一行中的间隔,之后很容易计算每个州的累积时间。

您还可以查看this question。它似乎与你的几乎相似。

答案 5 :(得分:0)

感谢您的帮助。 我很惊讶细节是什么答案。 我会测试你的解决方案并通知你结果。 我再次对细节回答感到非常惊讶。

我测试了第一部分(机器状态0,1 i 2的总和时间),这没关系。 现在我将测试其余部分答案。

对我来说最大的问题是在班次过渡期间分时。 例: '6.10.2009 16:30:00',1 '6.10.2009 17:30:00',2 '6.10.2009 19:16:00',1

在16:30到17:00之间,机器处于状态1,那时我必须添加到班次1,并且17:00到17:30之间的时间机器处于状态1并且那时我必须添加到班次2。

但首先我会回答你,看看你是否已经为此做出了解决方案。

再次感谢

答案 6 :(得分:0)

CREATE PROCEDURE dbo.final  @shiftdate datetime, @shift int

AS
BEGIN

DECLARE
     @shiftstart  as datetime ,
     @shiftstop as datetime,
     @date_m as varchar(33),
    @timestart as char(8),
    @smjena as int,
     @ms_prev as int,
     @t_rad as int,
     @t_stop as int,
     @t_alarm as int

if @shift = 1
begin
     set @timestart = '08:00:00'
     set @smjena=9
end
if @shift = 2
begin
     set @timestart = '17:00:00'
     set @smjena=8
end
if @shift = 3
begin
    set @timestart = '01:00:00'
     set @smjena=7
end


SELECT @date_m = convert(varchar,  @shiftdate, 104) + ' ' + convert(varchar,  @timestart, 114)
set @shiftstart = convert(datetime,@date_m,104)



select @shiftstop = dateadd(hh,@smjena,@shiftstart)


create table #t(id int identity(1,1), ts datetime, ms tinyint);
insert #t select time_stamp, stanje_stroja from perini where perini.time_stamp between @shiftstart and @shiftstop order by perini.time_stamp



if (select count(#t.id)  from #t where #t.ts=@shiftstart)= 0
BEGIN
if (select count(perini.id) from perini where time_stamp < @shiftstart) > 0
  begin
  set @ms_prev = (select top 1 stanje_stroja from perini where time_stamp<@shiftstart order by time_stamp asc)
  insert #t values (@shiftstart,@ms_prev)
end
end


if (select count(#t.id)  from #t where #t.ts=@shiftstop)= 0
BEGIN
if (select count(perini.id) from perini where time_stamp > @shiftstop) > 0
 begin
 set @ms_prev = (select top 1 stanje_stroja from perini where time_stamp>@shiftstop order by time_stamp asc)
 insert #t values (@shiftstop,@ms_prev)
end
end

select * into #t1 from #t where 1=2
insert into #t1 select ts, ms from #t order by ts


create table #t3(stanje int, trajanje int)

insert into #t3 select a.ms as stanje, convert(int,sum(datediff(ss,b.ts, a.ts))/60) as trajanje  from
#t1 a left join #t1 b on a.id = b.id + 1
group by a.ms



set @t_rad = (select trajanje from #t3 where stanje = 2)
set @t_alarm = (select trajanje from #t3 where stanje = 1)
set @t_stop = (select trajanje from #t3 where stanje = 0)

insert into perini_smjene_new (smjena,t_rad, t_stop, t_alarm, time_stamp) values (@shift,@t_rad,@t_stop, @t_alarm, convert(datetime,  @shiftdate, 103))




select * from #t3


END