Расчет по пересекающимся периодам

Alex S
Дата: 19.02.2013 16:49:26
Такая задачка возникла: в системе учитыватся время работы сотрудника над некой задачей, есть таблица: id сотрудника, id задачи, время начала работы над задачей, время окончания работы. Нужно вывести время (минуты) которое сотрудник затратил на задачу, но с учетом следующего:
1) сотрудник мог несколько раз работать над задачей (несколько записей в таблице) - с этим все просто
2) сотрудник мог работать с несколькими задачами, тогда время, затраченное на том периоде, где выполнялось несколько задач должно распределяться между ними пропорционально их количеству. Т.е. если были пересекающиеся периоды с другими задачами то отнимать у текущей задачи половину (для 2-ух задач), треть (для 3-х) и т.д. времени в этих пересекающихся периодах.
Хотелось бы написать udf, но пока не соображу с какой стороны подобраться.

Упрощенно, данные для теста:

declare @t table(user int, task int, dbeg datetime, dend datetime)
insert into @t
select 1, 1, '20130101 00:00','20130104 14:00'
union
select 1, 1, '20130105 08:00','20130105 10:00'
union
select 1, 2, '20130102 09:00','20130102 11:00'
union
select 1, 3, '20130102 10:00','20130104 16:00'

ищем время, затраченное сотрудником 1 на задачу 1

"на словах" при этих данных должно быть так:

с '20130101 00:00' по '20130102 09:00' защитывается полное время
с '20130102 09:00' по '20130102 10:00' + 1/2 т.к. выполнялось 2 задачи
с '20130102 10:00' по '20130104 14:00' + 1/3 т.к. выполнялось 3 задачи
c '20130104 14:00' по '20130105 08:00' задача 1 не выполнялась - ничего не считаем
с '20130105 08:00' по '20130105 10:00' + полное время

Может уже было аналогичное - не нашел, подскажите ссылку плиз.
Паганель
Дата: 19.02.2013 16:51:06
Я в MS Project видел несколько лет назад кнопочку которая это считает
hallabud
Дата: 19.02.2013 17:47:37
Alex S,
странно получается, если над задачей 1 юзер 1 работал 3 дня, и в этот период поработал еще над задачей 2 всего несколько минут (например), так от задачи 1 полтора дня отнимать?
Alex S
Дата: 19.02.2013 18:00:08
hallabud,
В этом случае отнимать (несколько минут) / 2.
Если же и 1 и 2 задача длились 3 дня, даже если пользователь реально не по 50% времени уделял каждой задаче, то да - каждая по 1,5 дня.
Alex S
Дата: 19.02.2013 18:04:32
Паганель,
Тут скорее вопрос как на языке SQL к этому подступиться. Реализовать в коде delphi или c#, думаю, не проблема.
Сервер SQL2008.
Minamoto
Дата: 19.02.2013 18:22:36
Alex S, написал что-то - проверяйте внимательно:

with points as
(select distinct dbeg as point 
   from @t
 union
 select distinct dend 
   from @t
),
periods as
(select p.point as spoint, p2.point as epoint
   from points p
        cross apply (select top 1 point from points p1 where p1.point > p.point order by point) as p2
),
taskperiods as (
select task, datediff(mi, spoint, epoint)/COUNT(*) over (partition by spoint, epoint) as busytime
  from periods
       inner join @t t
               on periods.spoint >= t.dbeg
              and periods.epoint <= t.dend)
select task, SUM(busytime) as busytime
  from taskperiods
  group by task
Minamoto
Дата: 19.02.2013 18:24:17
Minamoto
Alex S, написал что-то - проверяйте внимательно:

Результат в минутах, если нужна другая точность - меняйте в datediff
aleks2
Дата: 19.02.2013 19:46:29
declare @t table(usr int, task int, dbeg datetime, dend datetime)
insert into @t
select 1, 1, '20130101 00:00','20130104 14:00'
union
select 1, 1, '20130105 08:00','20130105 10:00'
union
select 1, 2, '20130102 09:00','20130102 11:00'
union
select 1, 3, '20130102 10:00','20130104 16:00'

declare @tt table(usr int, task int, dt datetime, inc int, n int identity primary key clustered)

insert @tt
select usr, task, dbeg, 1 from @t
union all
select usr, task, dend, -1 from @t
order by usr, dbeg, task


select x.usr, x.cnt, x.dt as beg, y.dt as [end]
from
(select t.usr, t.task, t.dt, t.inc, t.n, sum(tt.inc) as cnt
  from @tt t left outer join @tt tt on t.usr=tt.usr and t.n>=tt.n 
  group by t.usr, t.task, t.dt, t.inc, t.n
 ) x
 inner join
 @tt y
 on x.usr=y.usr and x.n+1=y.n
Cygapb-007
Дата: 19.02.2013 20:32:06
Каюсь, идею с временной таблицей подсмотрел, сначала было как в комментах
/*with 
reorg as (
   select 
      row_number() over (order by d.dt) rn, d.dt,
      (select COUNT(*) from @t c where c.usr=d.usr and d.dt between c.dbeg and c.dend and d.dt<c.dend) cnt
   from @t t
   cross apply(select t.dbeg dt union select t.dend) d
   ),*/
declare @table table (rn int identity, dt datetime, cnt int)
insert @table
select d.dt,(select COUNT(*) from @t c where d.dt between c.dbeg and c.dend and d.dt<c.dend) cnt
   from @t t
   cross apply(select t.dbeg dt union select t.dend) d
;with 
timing as (
   select r.rn, r.dt, DATEDIFF(MINUTE, r.dt, n.dt)mm, r.cnt
   from @table r 
   join @table n on n.rn=r.rn+1
   )
select t.*, c.dt, c.mm, c.cnt
from @t t
join timing c on t.dbeg<=c.dt and c.dt<t.dend 
order by t.usr, t.task, t.dbeg, c.dt
Cygapb-007
Дата: 19.02.2013 20:37:30
ээ... а в INSERT забыл добавить ORDER BY D.DT