T-sql Summarizing Differences Between Timestamps

I track the state of the machine, which can be 0.1 and 2, and store this data in the sql table using time_stamp. I have a table in sql server with the following fields: identifier (integer) time_stamp (DateTime) machine_state (integer)

The condition of the machine is related to the condition of the machine:
machine_state = 0 - the machine is tilted
machine_state = 1-machine with an alarm
machine_state = 2-machine start

Now I want to calculate how many cars were in each state in each shift. Shifts

  • 8 a.m. - 5 p.m.
  • 17: 00-01: 00
  • 01: 00-08 :. 00

My problem is how can I calculate the time of each state of the machine (sum_time_0, sum_time_1, sum_time_2) and group this time using a shift. I want to calculate the time in seconds and then convert to minutes.

To improve the image, I exported part of the table

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 

Any advice would help. Thanks in advance.

+2
source share
7 answers

You can join the next computer state for each row, then group by state and summarize the time difference ...

 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 #tt 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 
+2
source

Here is a diagram of how I will do this. I make some assumptions that may be invalid or not applicable to your situation, so I do not code everything.

First, I would break the problem into pieces: compute data for one shift at a time. (I assume that you run this once a day, or perhaps once a week.)

I would implement this as a stored procedure with two parameters:

  • @ShiftDate, specifying the date to be calculated (use only part of the date, ignore any time value)
  • @Shift, indicating which shift to analyze (1, 2, 3, as you defined)

Create two “full” days, one for the start of the shift, one for the end. For example, if @ShiftDate = 'October 22, 2009' and @Shift = 2, you will get

  • @ShiftStart = 'October 22, 2009 5:00 p.m.
  • @ShiftStop = 'October 23, 2009 1:00:00

Create a temporary table to store a subset of the data that we will analyze. Fill it like this:

  • Copy all data between @ShiftStart and @ShiftStop
  • DO NOT include data in which sequential (in time) records have the same state. If such data exists, discard everything except the earliest record. (It looks like your data is being generated this way, but do you want the data to always be good?)
  • Add a column for a uniformly increasing counter (1, 2, 3, etc.). It looks like you already have this, but again, you want to be here.

Then check if entries are present for both @ShiftStart and @ShiftStop. If there are no such entries:

  • For @ShiftStart, create a record and set machine_state regardless of the value from the most recent record to @ShiftStart
  • For @ShiftStop, create a record and set machine_state, well, anything, since we will not refer to this value
  • In both cases, make sure you set the counter column correctly (@ShiftStart counter is one less than the earliest value, @ShiftStops counter is larger than the last value)
  • (The above is why you make this a temporary table. If you cannot load these dummy rows, you will have to use the procedural code to view the tables, which is the kind of procedural code that drowns out the database servers.)

You need these records in order to get data for the time between the beginning of the shift and the first recorded record in this shift, as well as at the end of the shift.

At this moment, the elements are ordered in time with a uniformly increasing counter column (1, 2, 3). Assuming all of the above, the following query should return the data you are looking for:

 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 

Notes:

  • This is written for MS SQL Server. Your language syntax may vary.
  • I have not tested this code. Any typos were intentionally included to keep your final version above mine.
  • EXPORT_TABLE is the temporary table described above.

  • In MS SQL, dividing the sum of an integer by an integer will produce a truncated integer, i.e. 59 seconds will turn into 0 minutes. If you need higher precision, dividing by 60.0 will result in a decimal value.

This is just a frame. I think you can surpass this in any conditions that you have to deal with.

+1
source

You can use an exclusive join to find the previous line:

 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 

Then the request is grouped by computer state. The result is different from the other answers here, I'm curious which one is right!

 State MinutesInState 1 54 2 140 

Here is an example of the data I used:

 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 
+1
source

If you just want fast and dirty, this will do:

 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 

And go from there.

But this method and some similar methods on this page related to non-equijoin will not scale well with volume. To process a large amount of data, we must use different methods.

Your identifier is displayed sequentially. It? This may be helpful. If not, we must create it.

 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 

As soon as we have a serial id, we can connect on it:

 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 

From here, your request should be written by yourself. Pay attention to the state of the machine that overlaps the shift.

This method, although it looks expensive, will scale well with volume. You order data once and join once. If the identifier is sequential, you can skip the first step, make sure that id has a clustered primary key, and join the identifier, not seqno.

If you have a really large amount of data, you do this instead:

 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 

This will disable all other methods. And if your identifier is in the correct order (it does not have to be sequential, only in the correct order), you can skip the first step and instead define a clustered index on id if it does not already exist.

+1
source

You can do something like this:

 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 will return you an interval of one line, after which it is easy to calculate the total time for each state.

You can also look at this question . It seems to be almost like yours.

0
source

thanks for the help. I was surprised how detailed the answer is. I will make your decision and let you know the results. Again I am very surprised at the detailed answer.

I checked the first part (summarize the state time of the machine 0, 1, 2), and this is normal. Now I will spend the rest of the answer.

The biggest problem for me was splitting during the transition. example: '6/10/2009 16:30:00', 1 '6/10/2009 17:30:00', 2 '6/10/2009 19:16:00', 1

The time between 16:30 and 17:00 the machine was at state 1, and at this time I should add 1 to the shift, and the time between 17:00 and 17:30 the machine was at 1, and at this time I need to add shift 2.

But first, I will go through the fact that you will answer that you have already made a decision for this.

thanks again

0
source
 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 
0
source

All Articles