Поиск начальных и конечных дат из таблицы номеров дат (длительности дат)

StackOverflow https://stackoverflow.com/questions/1019661

Вопрос

У меня есть две таблицы: таблица расписаний, которая содержит информацию о том, как запланирован сотрудник, и таблица чисел, в которой каждое число соответствует дате.

Таблицы выглядят так:

[Employee Schedule]

ID          Employee ID Project ID  Day ID
----------- ----------- ----------- -----------
1           64          2           168
2           64          2           169
3           64          2           170
4           64          2           171
5           64          1           169
6           64          1           170
7           64          1           171
8           64          1           172
9           64          2           182
10          64          2           183
11          64          2           184

и

[Day Numbers]

ID          Day
----------- ----------
168         2009-06-18
169         2009-06-19
170         2009-06-20
171         2009-06-21
172         2009-06-22
173         2009-06-23
174         2009-06-24
175         2009-06-25
176         2009-06-26
177         2009-06-27
178         2009-06-28
179         2009-06-29
180         2009-06-30
181         2009-07-01
182         2009-07-02
183         2009-07-03
184         2009-07-04

Как видите, сотрудник 64 запланирован для проекта 1 с 2009-06-19 по 2009-06-22 и проекта 2 с 2009-06-18 по 2009-06-21 и снова с 2009-07-02. до 2009-07-04.

У меня вопрос: какой алгоритм я могу использовать для быстрого определения интервалов в расписании сотрудника таким образом, чтобы я мог отобразить его следующим образом?

 Employee ID Project ID Duration
 ----------- ---------- ------------
 64          1          2009-06-19 to 2009-06-22
 64          2          2009-06-18 to 2009-06-21
 64          2          2009-07-02 to 2009-07-04

Я могу сделать это на стороне SQL или кода. У меня есть Linq, если мне это нужно. Таблицу не нужно компилировать с помощью SQL. Это будет происходить динамически на веб-сайте и должно быть максимально эффективным. Я не хочу повторять каждый из них и искать разрывы в смежные дни, если не буду этого делать.

Это было полезно?

Решение

Предполагая, что идентификаторы дня всегда последовательны для частичного решения ...

select *
  from employee_schedule a                    
 where not exists( select *                          
                     from employee_schedule b        
                    where a.employeeid = b.employeeid
                      and a.projectid  = b.projectid 
                      and (a.dayid - 1) = b.dayid )

перечисляет идентификаторы начального дня:

 ID      EMPLOYEEID       PROJECTID           DAYID 
 1              64               2             168 
 5              64               1             169 
 9              64               2             182 



select *
  from employee_schedule a                   
 where not exists( select *                         
                     from employee_schedule b       
                    where a.employeeid = b.employeei
                      and a.projectid  = b.projectid
                      and (a.dayid + 1) = b.dayid )

перечисляет идентификаторы на конец дня:

  ID      EMPLOYEEID       PROJECTID           DAYID 
  4              64               2             171 
  8              64               1             172 
 11              64               2             184 

Другие советы

Давайте сделаем вид, чтобы сделать вещи проще:

create view EmployeeProjectDates
as
select
    e.[Employee ID], e.[Project ID], d.Day
from
    [Employee Scchedule] e
    join [Day Numbers] d on e.[Day Id] = d.ID

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

select
    one.[Employee ID], one.[Project ID], one.Day as StartDate
from
    EmployeeProjectDays one
    left join EmployeeProjectDays two on one.[Employee ID] = two.[Employee ID] and one.[Project ID] = two.[Project ID] and one.Day = DATEADD(DAY, 1, two.Day)
where
    two.Day is null

А затем выполните аналогичный запрос, чтобы получить даты окончания и сопоставить их. Думаю, что-то подобное поможет вам обоим.

select
    one.[Employee ID], one.[Project ID], one.Day as StartDate,
    (select
        min(two_end.Day)
    from
        EmployeeProjectDays one_end
        join EmployeeProjectDays two_end on one_end.[Employee ID] = two_end.[Employee ID] and one_end.[Project ID] = two_end.[Project ID] and one.Day = DATEADD(DAY, 1, two.Day)
     where
        one_end.Day is null
        and two_end.Day > one.Day) as EndDate
from
    EmployeeProjectDays one
    left join EmployeeProjectDays two on one.[Employee ID] = two.[Employee ID] and one.[Project ID] = two.[Project ID] and one.Day = DATEADD(DAY, 1, two.Day)
where
    two.Day is null

Я не проверял ни один из этих запросов, но что-то подобное должно работать. Мне пришлось использовать подобный запрос, прежде чем мы что-то реализовали в коде нашего приложения, чтобы найти даты начала и окончания.

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

create table schedule (id number, employee_id number, project_id number, day_id number);

insert into schedule (id, employee_id, project_id, day_id)
values(1,64,2,168);
insert into schedule (id, employee_id, project_id, day_id)
values(2,64,2,169);
insert into schedule (id, employee_id, project_id, day_id)
values(3,64,2,170);
insert into schedule (id, employee_id, project_id, day_id)
values(4,64,2,171);
insert into schedule (id, employee_id, project_id, day_id)
values(5,64,1,169);
insert into schedule (id, employee_id, project_id, day_id)
values(6,64,1,170);
insert into schedule (id, employee_id, project_id, day_id)
values(7,64,1,171);
insert into schedule (id, employee_id, project_id, day_id)
values(8,64,1,172);
insert into schedule (id, employee_id, project_id, day_id)
values(9,64,2,182);
insert into schedule (id, employee_id, project_id, day_id)
values(10,64,2,183);
insert into schedule (id, employee_id, project_id, day_id)
values(11,64,2,184);
insert into schedule (id, employee_id, project_id, day_id)
values(11,65,3,184);

select * 
FROM (
    select  
        employee_id,
        project_id,
        first_day,
        nvl(last_day, 
            lead(last_day) over (
                partition by employee_id, project_id 
                order by nvl(first_day, last_day)
            )
        ) last_day
    from (
        select -- this identifies start and end rows of an interval
            employee_id,
            project_id,
            decode (day_id - prev_day, 1, null, day_id) first_day, -- uses day_id, if prev_day is not really the previous day, i.e. a gap or null
            decode (day_id - next_day, -1, null, day_id) last_day
        from (
            select -- this select adds columns for the previous and next day, in order to identify the boundaries of intervals 
                employee_id, 
                project_id, 
                day_id, 
                lead(day_id) over ( 
                    partition by employee_id, project_id 
                    order by day_id
                ) next_day,
                lag(day_id) over ( 
                    partition by employee_id, project_id 
                    order by day_id
                ) prev_day
            from schedule
        )
    )
    where first_day is not null 
    or last_day is not null-- just filter the rows, that represent start or end dates
) 
where first_day is not null

производит этот вывод:

64  1   169 172
64  2   168 171
64  2   182 184
65  3   184 184

Я не проверял, но попробуйте:

select [Employee ID], [Project ID], start + ' to ' + end
from (
    select s.[Employee ID], s.[Project ID], min(d.Day) start, max(d.Day) end
    from [Employee Schedule] s
    inner join [Day Numbers] d on s.[Day ID] = d.[Day ID]
    group by s.[Employee ID], s.[Project ID]
) a

Изменить: исправлены некоторые имена столбцов

Для упрощения запросов я рекомендую изменить схему на:

[EmployeeSchedule]

ID 
EmployeeID 
ProjectID  
StartDate 
EndDate

и полностью избавься от номеров дня. Это сделает ваши запросы более простыми, эффективными и позволит вам иметь записи с NULL StartDates или EndDates, если хотите.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top