Как рассчитать самую длинную полосу в SQL? - PullRequest
5 голосов
/ 16 июня 2010

У меня есть

  TABLE EMPLOYEE - ID,DATE,IsPresent

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

Select Id,Count(*) from Employee where IsPresent=1

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

Ответы [ 5 ]

4 голосов
/ 16 июня 2010

групповой отсутствует.

Для выбора общего количества человеко-дней (для каждого) посещаемости всего офиса.

Select Id,Count(*) from Employee where IsPresent=1

Выбор посещаемости в человеко-днях на одного сотрудника.

Select Id,Count(*)
from Employee
where IsPresent=1
group by id;

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

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

create tmpdb.absentdates as
Select id, date, today as date2
from EMPLOYEE
where IsPresent=0
order by id, date;

Таким образом, хитрость заключается в том, чтобы вычислить разницу дат между двумя отсутствующими днями, чтобы найти продолжительность непрерывно присутствующих дней. Теперь заполните date2 следующей отсутствующей датой на сотрудника. Самая последняя запись на сотрудника не будет обновлена, но останется со значением сегодня, потому что в базе данных нет записи с более высокой датой, чем сегодня.

update tmpdb.absentdates
set date2 = 
  select min(a2.date)
  from
   tmpdb.absentdates a1,
   tmpdb.absentdates a2
  where a1.id = a2.id
    and a1.date < a2.date

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

create tmpdb.absentdatesX as
Select id, date
from EMPLOYEE
where IsPresent=0
order by id, date;

create tmpdb.absentdates as
select *, today as date2
from tmpdb.absentdatesX;

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

insert into tmpdb.absentdates a
select a.id, min(e.date), today
from EMPLOYEE e
where a.id = e.id

Теперь обновите date2 следующей более поздней отсутствующей датой, чтобы иметь возможность выполнить date2 - date.

update tmpdb.absentdates
set date2 = 
  select min(x.date)
  from
   tmpdb.absentdates a,
   tmpdb.absentdatesX x
  where a.id = x.id
    and a.date < x.date

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

select id, datediff(date2, date) as continuousPresence
from tmpdb.absentdates
group by id, continuousPresence
order by id, continuousPresence

Но вы хотите только самую длинную полосу:

select id, max(datediff(date2, date) as continuousPresence)
from tmpdb.absentdates
group by id
order by id

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

Таким образом, мы зависим от количества записей в качестве законных рабочих дней.

create tmpdb.absentCount as
Select a.id, a.date, a.date2, count(*) as continuousPresence
from EMPLOYEE e, tmpdb.absentdates a
where e.id = a.id
  and e.date >= a.date
  and e.date < a.date2
group by a.id, a.date
order by a.id, a.date;

Помните, что каждый раз, когда вы используете агрегатор, такой как count, ave Вам необходимо сгруппировать выбранный список элементов, так как по ним нужно объединяться.

Теперь выберите максимальную полосу

select id, max(continuousPresence)
from tmpdb.absentCount
group by id

Чтобы перечислить даты серии:

select id, date, date2, continuousPresence
from tmpdb.absentCount
group by id
having continuousPresence = max(continuousPresence);

Могут быть некоторые ошибки (sql server tsql) выше, но это общая идея.

4 голосов
/ 16 июня 2010

РЕДАКТИРОВАТЬ Вот версия запроса SQL Server:

with LowerBound as (select second_day.EmployeeId
        , second_day."DATE" as LowerDate
        , row_number() over (partition by second_day.EmployeeId 
            order by second_day."DATE") as RN
    from T second_day
    left outer join T first_day
        on first_day.EmployeeId = second_day.EmployeeId
        and first_day."DATE" = dateadd(day, -1, second_day."DATE")
        and first_day.IsPresent = 1
    where first_day.EmployeeId is null
    and second_day.IsPresent = 1)
, UpperBound as (select first_day.EmployeeId
        , first_day."DATE" as UpperDate
        , row_number() over (partition by first_day.EmployeeId 
            order by first_day."DATE") as RN
    from T first_day
    left outer join T second_day
        on first_day.EmployeeId = second_day.EmployeeId
        and first_day."DATE" = dateadd(day, -1, second_day."DATE")
        and second_day.IsPresent = 1
    where second_day.EmployeeId is null
    and first_day.IsPresent = 1)
select LB.EmployeeID, max(datediff(day, LowerDate, UpperDate) + 1) as LongestStreak
from LowerBound LB
inner join UpperBound UB
    on LB.EmployeeId = UB.EmployeeId
    and LB.RN = UB.RN
group by LB.EmployeeId

Версия тестовых данных SQL Server:

create table T (EmployeeId int
    , "DATE" date not null
    , IsPresent bit not null 
    , constraint T_PK primary key (EmployeeId, "DATE")
)


insert into T values (1, '2000-01-01', 1);
insert into T values (2, '2000-01-01', 0);
insert into T values (3, '2000-01-01', 0);
insert into T values (3, '2000-01-02', 1);
insert into T values (3, '2000-01-03', 1);
insert into T values (3, '2000-01-04', 0);
insert into T values (3, '2000-01-05', 1);
insert into T values (3, '2000-01-06', 1);
insert into T values (3, '2000-01-07', 0);
insert into T values (4, '2000-01-01', 0);
insert into T values (4, '2000-01-02', 1);
insert into T values (4, '2000-01-03', 1);
insert into T values (4, '2000-01-04', 1);
insert into T values (4, '2000-01-05', 1);
insert into T values (4, '2000-01-06', 1);
insert into T values (4, '2000-01-07', 0);
insert into T values (5, '2000-01-01', 0);
insert into T values (5, '2000-01-02', 1);
insert into T values (5, '2000-01-03', 0);
insert into T values (5, '2000-01-04', 1);
insert into T values (5, '2000-01-05', 1);
insert into T values (5, '2000-01-06', 1);
insert into T values (5, '2000-01-07', 0);

Извините, это написанов Oracle замените соответствующую арифметику даты SQL Server.

Допущения:

  • Дата - это либо значение Date, либо DateTime с компонентом времени 00:00:00.
  • Первичный ключ - (EmployeeId, Date)
  • Все поля: not null
  • Если для сотрудника отсутствует дата, они были не настоящее время.(Используется для обработки начала и конца ряда данных, но также означает, что пропущенные даты в середине будут прерывать полосы. Это может быть проблемой в зависимости от требований.

    with LowerBound as (select second_day.EmployeeId
            , second_day."DATE" as LowerDate
            , row_number() over (partition by second_day.EmployeeId 
                order by second_day."DATE") as RN
        from T second_day
        left outer join T first_day
            on first_day.EmployeeId = second_day.EmployeeId
            and first_day."DATE" = second_day."DATE" - 1
            and first_day.IsPresent = 1
        where first_day.EmployeeId is null
        and second_day.IsPresent = 1)
    , UpperBound as (select first_day.EmployeeId
            , first_day."DATE" as UpperDate
            , row_number() over (partition by first_day.EmployeeId 
                order by first_day."DATE") as RN
        from T first_day
        left outer join T second_day
            on first_day.EmployeeId = second_day.EmployeeId
            and first_day."DATE" = second_day."DATE" - 1
            and second_day.IsPresent = 1
        where second_day.EmployeeId is null
        and first_day.IsPresent = 1)
    select LB.EmployeeID, max(UpperDate - LowerDate + 1) as LongestStreak
    from LowerBound LB
    inner join UpperBound UB
        on LB.EmployeeId = UB.EmployeeId
        and LB.RN = UB.RN
    group by LB.EmployeeId
    

ТестДанные:

    create table T (EmployeeId number(38) 
        , "DATE" date not null check ("DATE" = trunc("DATE"))
        , IsPresent number not null check (IsPresent in (0, 1))
        , constraint T_PK primary key (EmployeeId, "DATE")
    )
    /

    insert into T values (1, to_date('2000-01-01', 'YYYY-MM-DD'), 1);
    insert into T values (2, to_date('2000-01-01', 'YYYY-MM-DD'), 0);
    insert into T values (3, to_date('2000-01-01', 'YYYY-MM-DD'), 0);
    insert into T values (3, to_date('2000-01-02', 'YYYY-MM-DD'), 1);
    insert into T values (3, to_date('2000-01-03', 'YYYY-MM-DD'), 1);
    insert into T values (3, to_date('2000-01-04', 'YYYY-MM-DD'), 0);
    insert into T values (3, to_date('2000-01-05', 'YYYY-MM-DD'), 1);
    insert into T values (3, to_date('2000-01-06', 'YYYY-MM-DD'), 1);
    insert into T values (3, to_date('2000-01-07', 'YYYY-MM-DD'), 0);
    insert into T values (4, to_date('2000-01-01', 'YYYY-MM-DD'), 0);
    insert into T values (4, to_date('2000-01-02', 'YYYY-MM-DD'), 1);
    insert into T values (4, to_date('2000-01-03', 'YYYY-MM-DD'), 1);
    insert into T values (4, to_date('2000-01-04', 'YYYY-MM-DD'), 1);
    insert into T values (4, to_date('2000-01-05', 'YYYY-MM-DD'), 1);
    insert into T values (4, to_date('2000-01-06', 'YYYY-MM-DD'), 1);
    insert into T values (4, to_date('2000-01-07', 'YYYY-MM-DD'), 0);
    insert into T values (5, to_date('2000-01-01', 'YYYY-MM-DD'), 0);
    insert into T values (5, to_date('2000-01-02', 'YYYY-MM-DD'), 1);
    insert into T values (5, to_date('2000-01-03', 'YYYY-MM-DD'), 0);
    insert into T values (5, to_date('2000-01-04', 'YYYY-MM-DD'), 1);
    insert into T values (5, to_date('2000-01-05', 'YYYY-MM-DD'), 1);
    insert into T values (5, to_date('2000-01-06', 'YYYY-MM-DD'), 1);
    insert into T values (5, to_date('2000-01-07', 'YYYY-MM-DD'), 0);
1 голос
/ 16 июня 2010

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

with LowerBound as (select second_day.EmployeeId
        , second_day."DATE" as LowerDate
        , row_number() over (partition by second_day.EmployeeId 
            order by second_day."DATE") as RN
    from T second_day
    left outer join T first_day
        on first_day.EmployeeId = second_day.EmployeeId
        and first_day."DATE" = dateadd(day, -1, second_day."DATE")
        and first_day.IsPresent = 1
    where first_day.EmployeeId is null
    and second_day.IsPresent = 1)
, UpperBound as (select first_day.EmployeeId
        , first_day."DATE" as UpperDate
        , row_number() over (partition by first_day.EmployeeId 
            order by first_day."DATE") as RN
    from T first_day
    left outer join T second_day
        on first_day.EmployeeId = second_day.EmployeeId
        and first_day."DATE" = dateadd(day, -1, second_day."DATE")
        and second_day.IsPresent = 1
    where second_day.EmployeeId is null
    and first_day.IsPresent = 1)
select LB.EmployeeID, max(datediff(day, LowerDate, UpperDate) + 1) as LongestStreak
from LowerBound LB
inner join UpperBound UB
    on LB.EmployeeId = UB.EmployeeId
    and LB.RN = UB.RN
group by LB.EmployeeId

go

with NumberedRows as (select EmployeeId
        , "DATE"
        , IsPresent
        , row_number() over (partition by EmployeeId
            order by "DATE") as RN
--        , min("DATE") over (partition by EmployeeId, IsPresent) as MinDate
--        , max("DATE") over (partition by EmployeeId, IsPresent) as MaxDate
    from T)
, LowerBound as (select SecondRow.EmployeeId
        , SecondRow.RN
        , row_number() over (partition by SecondRow.EmployeeId 
            order by SecondRow.RN) as LowerBoundRN
    from NumberedRows SecondRow
    left outer join NumberedRows FirstRow
        on FirstRow.IsPresent = 1
        and FirstRow.EmployeeId = SecondRow.EmployeeId
        and FirstRow.RN + 1 = SecondRow.RN
    where FirstRow.EmployeeId is null
    and SecondRow.IsPresent = 1)
, UpperBound as (select FirstRow.EmployeeId
       , FirstRow.RN
       , row_number() over (partition by FirstRow.EmployeeId
            order by FirstRow.RN) as UpperBoundRN
    from NumberedRows FirstRow
    left outer join NumberedRows SecondRow
        on SecondRow.IsPresent = 1
        and FirstRow.EmployeeId = SecondRow.EmployeeId
        and FirstRow.RN + 1 = SecondRow.RN
    where SecondRow.EmployeeId is null
    and FirstRow.IsPresent = 1)
select LB.EmployeeId, max(UB.RN - LB.RN + 1)
from LowerBound LB 
inner join UpperBound UB
    on LB.EmployeeId = UB.EmployeeId
    and LB.LowerBoundRN = UB.UpperBoundRN
group by LB.EmployeeId
1 голос
/ 16 июня 2010

Попробуйте это:

select 
    e.Id,
    e.date,
    (select 
       max(e1.date) 
     from 
       employee e1 
     where 
       e1.Id = e.Id and
       e1.date < e.date and 
       e1.IsPresent = 0) StreakStartDate,
    (select 
       min(e2.date) 
     from 
       employee e2 
     where 
       e2.Id = e.Id and
       e2.date > e.date and
       e2.IsPresent = 0) StreakEndDate           
from 
    employee e
where
    e.IsPresent = 1

Затем выясняет самую длинную полосу для каждого сотрудника:

select id, max(datediff(streakStartDate, streakEndDate))
from (<use subquery above>)
group by id

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

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

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

0 голосов
/ 16 июня 2010

Я сделал это один раз, чтобы определить, сколько дней подряд пожарный находился на смене не менее 15 минут.

Ваш случай немного проще.

Если вы хотите предположить, что ни один сотрудник не приходил более 32 раз подряд, вы можете просто использовать выражение общего стола. Но лучшим подходом было бы использовать временную таблицу и цикл while.

Вам понадобится столбец с именем StartingRowID. Продолжайте присоединяться из своей временной таблицы к таблице employeeWorkDay в течение следующего последовательного рабочего дня сотрудника и вставляйте их обратно в временную таблицу. Когда @@ Row_Count = 0, вы захватили самую длинную полосу.

Теперь агрегируйте по StartingRowID, чтобы получить первый день самой длинной полосы. У меня мало времени, или я бы добавил пример кода.

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