Поиск начальных и конечных дат из таблицы номеров дат (длительности дат)
Вопрос
У меня есть две таблицы: таблица расписаний, которая содержит информацию о том, как запланирован сотрудник, и таблица чисел, в которой каждое число соответствует дате.
Таблицы выглядят так:
[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, если хотите.