Я имею
TABLE EMPLOYEE - ID,DATE,IsPresent
Я хочу вычислить самую длинную полосу для присутствия сотрудника. Существующий бит будет ложью в течение многих дней, он не приехал.. Таким образом, я хочу вычислить самое долгое количество дней, он пришел к власти для последовательных дат.. У меня есть поле столбца Date, уникально... Таким образом, я попробовал этот путь -
Select Id,Count(*) from Employee where IsPresent=1
Но вышеупомянутое не работает... Кто-либо может вести меня к тому, как я могу вычислить полосу для этого?.... Я уверен, что люди столкнулись с этим... Я пытался искать онлайн, но... не понял это хорошо..., выручите меня..
groupby отсутствует.
Выбрать общую посещаемость всего офиса в человеко-днях (для каждого).
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
Однако вышеупомянутое все еще проблематично, потому что dateiff не учитывает праздники и выходные.
Таким образом, мы полагаемся на количество записей как на законные рабочие дни.
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) выше, но это общая идея.
EDIT Вот 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.
Допущения:
(EmployeeId, Date)
not null
Если для сотрудника отсутствует дата, то он был not. (Используется для обработки начала и конца ряда данных, но также означает, что пропущенные даты в середине будут нарушать ряд. Может быть проблемой в зависимости от требований.
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
из T второй_день
left outer join T first_day
on first_day.EmployeeId = second_day.EmployeeId
and first_day. "DATE" = second_day. "DATE" - 1
и first_day.IsPresent = 1
где first_day.EmployeeId - 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
из T первый_день
left outer join T second_day
on first_day.EmployeeId = second_day.EmployeeId
and first_day. "DATE" = second_day. "DATE" - 1
и second_day.IsPresent = 1
где second_day.EmployeeId - null
и first_day.IsPresent = 1)
select LB.EmployeeID, max(UpperDate - LowerDate + 1) as LongestStreak
из LowerBound LB
inner join UpperBound UB
on LB.EmployeeId = UB.EmployeeId
и LB.RN = UB.RN
group by LB.EmployeeId
Test Data:
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);
Попробуйте следующее:
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
Я не совсем уверен, что этот запрос имеет правильный синтаксис, потому что у меня сейчас нет базы данных. Также обратите внимание, что столбцы начала и конца полосы содержат не первый и последний день, когда сотрудник присутствовал, а ближайшие даты, когда он отсутствовал. Если даты в таблице имеют примерно одинаковое расстояние, это не означает, что в противном случае запрос станет немного более сложным, потому что нам нужно узнать ближайшие даты присутствия. Также эти улучшения позволяют справиться с ситуацией, когда самая длинная полоса является первой или последней.
Основная идея состоит в том, чтобы для каждой даты, когда присутствовал сотрудник, узнавать начало и конец серии.
Для каждой строки в таблице, когда сотрудник присутствовал, начало полосы - это максимальная дата, которая меньше даты текущей строки, когда сотрудник отсутствовал.
Вот альтернативная версия, позволяющая по-другому обрабатывать пропущенные дни. Предположим, вы записываете запись только для рабочих дней, а работа с понедельника по пятницу в одну неделю и с понедельника по пятницу следующей недели считается за десять последовательных дней. В этом запросе предполагается, что пропущенные даты в середине ряда строк являются нерабочими днями.
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
Я делал это однажды, чтобы определить последовательные дни, когда пожарный находился на смене не менее 15 минут.
Ваш случай немного более простой.
Если вы хотите предположить, что ни один сотрудник не приходил более 32 раз подряд, вы можете просто использовать общее выражение таблицы. Но лучшим подходом было бы использование временной таблицы и цикла while.
Вам понадобится столбец под названием StartingRowID. Продолжайте соединять данные из временной таблицы с данными таблицы employeeWorkDay для следующего подряд рабочего дня сотрудника и вставлять их обратно в временную таблицу. Когда @@Row_Count = 0, вы поймали самую длинную полосу.
Теперь выполните агрегацию по StartingRowID, чтобы получить первый день самой длинной полосы. У меня мало времени, иначе я бы включил пример кода.