Я ударил стену с этим запросом, и я прошу любой помощи по этому вопросу.
Фон: Мне нужно создать запрос, который идентифицирует все «неоплачиваемые» рабочие дни на основе данных, доступных в нашей базе данных Oracle.
Наши данные отслеживания времени и труда содержат только 1 столбец даты (EFF_DT), представляющий «дату работы».
Упрощенная версия необработанной таблицы выглядит следующим образом:
EEID | WORK_DT | КОД ОПЛАТЫ |
---|---|---|
1111 | 01.03.2022 | БЕЗ ОПЛАТЫ |
1111 | 02.03.2022 | REG_WORK |
1111 | 03.03.2022 | REG_WORK |
1111 | 04.03.2022 | БЕЗ ОПЛАТЫ |
1111 | 05.03.2022 | БЕЗ ОПЛАТЫ |
1111 | 06.03.2022 | БОЛЬНОЙ |
1111 | 07.03.2022 | БОЛЬНОЙ |
1111 | 08.03.2022 | БЕЗ ОПЛАТЫ |
1111 | 09.03.2022 | БЕЗ ОПЛАТЫ |
1111 | 10.03.2022 | БЕЗ ОПЛАТЫ |
Что мне нужно сделать, так это отфильтровать только строки «НЕОПЛАЧЕННЫЕ», а затем мне нужно определить «диапазон» дат начала/окончания работы неоплачиваемого отпуска.
Таким образом, ожидаемые результаты должны выглядеть следующим образом:
EEID | START_EFF_DT | END_EFF_DT |
---|---|---|
1111 | 01.03.2022 | 01.03.2022 |
1111 | 04.03.2022 | 05.03.2022 |
1111 | 08.03.2022 | 10.03.2022 |
Как мне подойти к разработке этого SQL для кристального отчета, который необходимо создать?
Любая помощь приветствуется!
Это известно как проблема пробелов и островов. Смотрите соответствующий пост здесь: stackoverflow.com/questions/30803021/…
@mathguy - между диапазоном дат определенно есть разрывы в датах (длинные выходные могут привести к такому разрыву дат). В этом сценарии, если код выплаты до и после разрыва является НЕОПЛАЧЕННЫМ, он по-прежнему считается последовательной строкой даты, т.е.: 24.03 - не оплачено 25.03 - не оплачено 28.03 - не оплачено 29.03 - не оплачено Отчет должен вывести: EEID: 1111; Start_Eff_Dt: 3/24; End_Eff_Dt 3/29
В Oracle 12.1 и более поздних версиях вы можете использовать match_recognize
для самых разных задач сопоставления с образцом.
Данные испытаний:
create table time_labor (eeid number, work_dt date, paycode varchar2(10));
alter session set nls_date_format = 'yyyy/mm/dd';
insert into time_labor (eeid, work_dt, paycode)
select 1111, to_date('2022/03/01'), 'UNPAID' from dual union all
select 1111, to_date('2022/03/02'), 'REG_WORK' from dual union all
select 1111, to_date('2022/03/03'), 'REG_WORK' from dual union all
select 1111, to_date('2022/03/04'), 'UNPAID' from dual union all
select 1111, to_date('2022/03/05'), 'UNPAID' from dual union all
select 1111, to_date('2022/03/06'), 'SICK' from dual union all
select 1111, to_date('2022/03/07'), 'SICK' from dual union all
select 1111, to_date('2022/03/08'), 'UNPAID' from dual union all
select 1111, to_date('2022/03/09'), 'UNPAID' from dual union all
select 1111, to_date('2022/03/10'), 'UNPAID' from dual
;
commit;
Запрос и вывод:
select eeid, start_eff_dt, end_eff_dt
from time_labor
match_recognize(
partition by eeid
order by work_dt
measures first(work_dt) as start_eff_dt,
last (work_dt) as end_eff_dt
pattern ( unpaid+ )
define unpaid as paycode = 'UNPAID'
);
EEID START_EFF_DT END_EFF_DT
---- ------------ ------------
1111 2022/03/01 2022/03/01
1111 2022/03/04 2022/03/05
1111 2022/03/08 2022/03/10
Механизм match_recognize
разделяет входные строки и упорядочивает их в каждом разделе в соответствии с предложениями partition by
и order by
. Он ищет совпадения шаблона unpaid+
, что означает одну или несколько последовательных строк, классифицированных как unpaid
, где эта классификация определена в предложении define
. (Здесь unpaid
— это просто мой выбор имени для этого классификатора; это кажется разумным выбором, но его можно назвать любым другим допустимым идентификатором, например x
или abc_2902
). match_recognize
по умолчанию возвращает одну строку на совпадение с дополнительными вычисляемыми выражениями из предложения measures
.
спасибо, что отправили меня в кроличью нору, чтобы узнать о match_recognize() и концепции проблем «промежутка и острова». Вчера я кое-что узнал, прочитав об этом. К сожалению, используемая нами база данных не является Oracle 12.1 :( Поэтому мне нужно будет найти другой метод для достижения желаемых результатов.
@Tommy - если ваша база данных такая старая (более чем на четыре версии отстает от текущей версии), вы должны четко указать это в своем вопросе. Какая является ваша версия? В разных версиях возможны разные решения. (Кроме того, я бы обновился как минимум до версии 12.1 даже только для match_recognize
, который является исключительно мощным инструментом для решения многих проблем.)
У меня нет большого доступа к этой базе данных, она предоставлена нашей третьей стороной, и у меня есть доступ только для чтения. Я подтвердил им сегодня, что он все еще на Oracle 11 .... Как бы ни был гладок предоставленный вами раствор, мне придется найти другой способ добиться этого :(
@Tommy - В Oracle 11.2 (и более ранних версиях) вы можете использовать очень хороший метод, известный как метод «фиксированных различий» или «табибито-сан», я опубликую отдельный ответ, чтобы показать, как это сделать.
Для пользователей более старых версий Oracle Database (до версии 12.1), которые не могут использовать предложение match_recognize
, вот решение, использующее только аналитические функции и очень хороший метод выявления пробелов и островов, известный как «фиксированные различия» или «табибито». метод "сан". Хитрость заключается в создании дополнительных выражений группировки, помеченных grp
, во вспомогательном подзапросе (который для простоты чтения я выделил в предложении with
).
with
prep as (
select eeid, work_dt, paycode,
row_number() over (partition by eeid order by work_dt)
- row_number() over (partition by eeid, paycode order by work_dt)
as grp
from time_labor
)
select eeid, min(work_dt) as start_eff_dt, max(work_dt) as end_eff_dt
from prep
group by eeid, paycode, grp
having paycode = 'UNPAID'
order by eeid, start_eff_dt
;
Образцы данных (для тестирования) и результаты этого запроса можно найти в другом ответе, в котором используется match_recognize
(только версии Oracle >= 12.1)
удивительно! спасибо @mathguy ... это SQL-решение - именно то, что я искал, чтобы завершить этот отчет
Для фиксированного EEID даты ВСЕГДА представляют собой полную последовательность? Или могут быть пробелы - например, есть строки за 25 марта и 28 марта, но не за два дня между ними? И если могут быть пробелы, как с этим справиться? Если "НЕОПЛАЧЕНО" как 25 марта, так и 28 марта, и нет строк для дат между ними?