2009-06-19 5 views
1

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

таблицы выглядит следующим образом:

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

+0

Вы можете сделать этот материал с аналитическими функциями, но я не знаю, если LINQ поддерживают их. –

+0

Можем ли мы предположить, что непрерывные идентификаторы в [Расписание сотрудников] составляют диапазон? Или только [День ID]? – gbn

+1

Можно предположить, что смежные кортежи [Идентификатор сотрудника], [Идентификатор проекта] и [День ID] задают диапазон. – Daniel

ответ

3

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

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

, даже если они не являются последовательными, запрос может быть переписан с использованием фактических дат с функцией DATEADD и тем же сопоставлением – van

0

Я не проверял, но попробовать:

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 

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

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

[EmployeeSchedule] 

ID 
EmployeeID 
ProjectID 
StartDate 
EndDate 

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

+1

Имеет ли это отношение к 2 диапазонам для projectid = 2? – gbn

+0

@gbn. Да. – Triptych

+0

Это не сработает. Между минимумом и максимумом могут быть пробелы, которые не должны включаться в продолжительность. – Daniel

1

Позволяет сделать вид, чтобы сделать вещи проще:

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 

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

+0

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

+0

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

1

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

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

Это прекрасное решение. Если бы в T-SQL были только отстающие и ведущие функции. – Daniel

Смежные вопросы