t-sql Суммирование различий между временными метками - PullRequest
2 голосов
/ 22 октября 2009

Я отслеживаю состояние машины, которое может быть 0,1 и 2, и хранить эти данные в таблице SQL с time_stamp. У меня есть таблица в SQL Server со следующими полями: идентификатор (целое) time_stamp (DateTime) 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

Любой совет поможет. Заранее спасибо.

Ответы [ 7 ]

2 голосов
/ 22 октября 2009

Вы можете присоединиться к следующему состоянию машины для каждой строки, затем сгруппировать по состоянию и суммировать разницу во времени ...

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 голос
/ 23 октября 2009

Если вы просто хотите быстро и грязно, это будет делать:

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

И иди оттуда.

Но этот метод и некоторые аналогичные методы на этой странице, в которых используется неравномерное распределение, не будут хорошо масштабироваться с объемом. Чтобы обрабатывать большой объем данных, мы должны использовать разные методы.

Ваш идентификатор отображается последовательно. Это? Это может быть полезно. Если нет, мы должны создать его.

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

Как только мы получим последовательный идентификатор, мы сможем присоединиться к нему:

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

Отсюда ваш запрос должен написать сам. Однако обратите внимание на состояния машины, которые перекрывают смену.

Этот метод, хотя и выглядит дорогим, хорошо масштабируется с объемом. Вы заказываете данные один раз и присоединяетесь один раз. Если идентификатор является последовательным, вы можете пропустить первый шаг, убедиться, что есть кластерный первичный ключ для идентификатора, и присоединиться к идентификатору, а не к 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

Это превзойдет все остальные методы. И если ваш идентификатор находится в правильном порядке (он не должен быть последовательным, просто в правильном порядке), вы можете пропустить первый шаг и вместо этого определить кластеризованный индекс для идентификатора, если он еще не существует.

1 голос
/ 22 октября 2009

Вы можете использовать эксклюзивное объединение, чтобы найти предыдущую строку:

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
1 голос
/ 22 октября 2009

Вот схема того, как я это сделаю. Я делаю некоторые предположения, которые могут быть недействительными или не применимы к вашей ситуации, поэтому я не все кодирую.

Сначала я разбил бы проблему на куски: вычисляю данные за одну смену за раз. (Полагаю, вы запускаете это раз в день или, может быть, раз в неделю.)

Я бы реализовал это как хранимую процедуру с двумя параметрами:

  • @ ShiftDate, указание даты для расчета (используйте только часть даты, игнорируйте любое значение времени)
  • @ Shift, указав, какой сдвиг анализировать (1, 2, 3, как вы определили)

Создайте два «полных» времени: один для начала смены, один для конца. Например, если @ShiftDate = '22.10.2009' и @Shift = 2, вы получите

  • @ ShiftStart = '22 октября 2009 г. 17:00:00'
  • @ ShiftStop = '23 октября 2009 г. 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 приведет к десятичному значению.

Это просто рамки. Я думаю, ты сможешь оправдать это на любых условиях, с которыми тебе придется иметь дело.

0 голосов
/ 16 ноября 2009
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 голосов
/ 30 октября 2009

спасибо за помощь. Я удивился, насколько подробно ответ. Я проверю ваше решение и сообщу вам о результате. Я снова очень удивлен подробным ответом.

Я протестировал первую часть (для суммирования времени состояния машины 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.

Но сначала я расскажу вам ответ, чтобы убедиться, что вы уже приняли решение для этого.

еще раз спасибо

0 голосов
/ 22 октября 2009

Вы можете сделать что-то вроде этого:

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)

Это вернет вам интервал в одной строке, после чего легко рассчитать суммарное время для каждого состояния.

Вы также можете посмотреть на этот вопрос . Кажется, он почти похож на твой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...