【问题标题】:t-sql Summing differences between timestampst-sql 对时间戳之间的差异求和
【发布时间】:2009-10-22 18:21:13
【问题描述】:

我正在跟踪机器状态,它可以是 0,1 和 2, 并使用 time_stamp 将该数据存储在 sql 表中。 我在 sql server 中有一个带有下一个字段的表: 编号(整数) 时间戳(日期时间) machine_state(int)

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

现在我想计算机器在每个班次中处于每个状态的时间。 班次是

  1. 8:00-17:00
  2. 17:00-01:00
  3. 01:00-08:00.

我的问题是如何计算机器每个状态的时间(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

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

【问题讨论】:

  • 您的样本数据不显示秒数。 17 点 17 分,机器明显地从状态 2 波动到 1 再回到 2,但没有办法知道它在每个状态中持续了多长时间。
  • 只要“真实”数据有秒,你就很好——否则我不知道你如何获得精确到秒的数据。

标签: sql sql-server tsql


【解决方案1】:

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

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 似乎正确,你知道“declare @blah table (..)”吗?它使您免于使用临时表以及它们随附的 drop/create 中可能的不匹配。
  • 我总是忘记表变量...另外我在 mssql2000 上测试过。如果保证 id 和时间戳保持同步,这将起作用,否则更改子查询中的 where 和 order by 以使用时间戳。
  • 嗨,这是求和时间的解决方案。但我的第二个问题是轮班过渡期间的时间。对我来说最大的问题是换班期间的时间分割。例如:'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。
【解决方案2】:

这是我如何做的大纲。我正在做出一些可能无效或不适用于您的情况的假设,因此我不会将所有内容都编码出来。

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

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

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

构建两个“完整”日期时间,一个用于轮班开始,一个用于结束。例如,如果 @ShiftDate = 'Oct 22, 2009' 和 @Shift = 2,你会得到

  • @ShiftStart = '2009 年 10 月 22 日 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 的计数器比最早的值小一,@ShiftStops 的计数器比最后一个值大一)
  • (以上就是为什么将其设为临时表的原因。如果无法加载这些虚拟行,则必须使用过程代码来遍历表,这是使数据库陷入困境的那种过程代码服务器。)

您需要这些条目来获取从班次开始到该班次内第一个记录条目之间的时间数据,以及班次结束时的数据。

此时,项目按时间排序,具有一致递增的计数器列 (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 会产生一个十进制值。

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

【讨论】:

  • 嗨,我确实听从了 Philip Kelley 的建议,并且确实得到了我想要的东西。与其他解决方案相比,该代码具有最佳性能。现在我正在寻找解决方案以在 sql 2005 express 中添加执行此过程的计划。我确实在 sql 2005 express 中发现了一些调度费用。如果有人对 slq express 的调度有一些建议,那就太好了。感谢大家对菲利普的特别好建议,其中确实给出了每个步骤的文字描述。谢谢
【解决方案3】:

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

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

【讨论】:

  • 是的,很酷的加入。优化器如何处理它?看起来非常昂贵。
  • @Peter:据我所知,如果您在连接中排除的列上有索引,这是最快的解决方案
  • 这还取决于 id 的顺序是否正确。 FWIW,2005 年的优化器声称 OUTER APPLY (SELECT TOP 1 ... 便宜 15%,即少一个连接的边际成本。它可能与实际表不同。我应该测试一下。
  • @Andomar:当您建议这种方法时,您是否考虑过并行执行?
  • @Peter:根本没想到!什么样的查询支持并行执行?
【解决方案4】:

如果您只想快速而肮脏,可以这样做:

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,我们就可以对它进行 equi-join:

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 上定义一个聚集索引,如果它还没有的话。

【讨论】:

  • 嗨,id是身份,我每天有600条数据。我将对此进行测试。我是这个论坛的新手,所以我有点困惑我如何与他人交流。谢谢
【解决方案5】:

你可以这样做:

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。它似乎和你的差不多。

【讨论】:

    【解决方案6】:

    感谢帮助。 我很惊讶答案有多详细。 我将测试您的解决方案并通知您结果。 我再次对详细的答案感到非常惊讶。

    我确实测试了第一部分(机器状态 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。

    但首先我会通过你的回答看看你是否已经为此制定了解决方案。

    再次感谢

    【讨论】:

      【解决方案7】:
      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
      

      【讨论】:

      • 嗨,我确实听从了 Philip Kelley 的建议,并且确实得到了我想要的东西。与其他解决方案相比,该代码具有最佳性能。现在我正在寻找解决方案以在 sql 2005 express 中添加执行此过程的计划。我确实在 sql 2005 express 中找到了一些调度费用。如果有人对 slq express 的调度有一些建议,那就太好了。感谢大家对菲利普的特别好建议,其中确实给出了每个步骤的文字描述。谢谢
      猜你喜欢
      • 2014-06-09
      • 2022-01-16
      • 2012-12-14
      • 2014-03-05
      • 2019-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多