如何获取表格中序列天数的开始日期和结束日期?

时间:2013-09-23 22:21:43

标签: sql oracle

我有一个包含以下列的表: 1. User_Id 2. Work_Date

create table Test_Seq(user_id number, work_date date);

它有以下数据:

insert into Test_Seq values (1, '01-SEP-2013');
insert into Test_Seq values (1, '02-SEP-2013');
insert into Test_Seq values  (1, '06-SEP-2013');
insert into Test_Seq values (1, '09-SEP-2013');
insert into Test_Seq values (1, '10-SEP-2013');

insert into Test_Seq values (2, '10-SEP-2013');
insert into Test_Seq values (2, '26-SEP-2013');
insert into Test_Seq values (2, '30-SEP-2013');
insert into Test_Seq values (2, '01-OCT-2013');

此表为用户存储work_date。这个work_date可能顺序也可能不顺序。

还有一张桌子:

create table temp_holidys (holiday date);
insert into temp_holidys values ('27-SEP-2013');
insert into temp_holidys values ('31-DEC-2013');

我需要查询/ pl sql来获取最后的Work_Date(按desc排序)及其相关的序列开始日期;星期六和星期日不会有任何记录,但仍将按顺序处理(日历日)。

与我们将Sat和Sun视为序列的一部分相同,如果那天在temp_holidys表中,它应该按顺序处理日期(参见下面的#2)。

  1. 对于user_id 1,这应该给我'10 -SEP-2013'作为结束日期,'06 -SEP-2013'作为开始日期
  2. 对于user_id 2,这应该给我'01 -OCT-2013'作为结束日期,'26 -SEP-2013'作为开始日期(27-OCT-2013需要按顺序处理,因为它在temp_holidys表)
  3. 必须是序列含义,例如在#1中,对于用户ID 1,如果没有'09 -SEP-2013'的记录,则应该返回'10 -SEP-2013'作为开始日期。同样在#2中,对于用户2,如果'26 -SEP-2013'上没有记录,则应该返回30-SEP-2013'作为开始日期。

3 个答案:

答案 0 :(得分:0)

你需要一个PL / SQL函数。既可以为您提供流水线输出,也可以告诉您天数是否相互跟随。以下是第二种方式的解决方案:

这是所需的功能。由于Oracle SQL中缺少布尔数据类型,它返回0表示false,1表示true表示:


create or replace function are_dates_adjacent(vi_start_date date, vi_end_date date) return number as
  v_count integer;
begin
  -- Same day or next day is of course in sequence with the start day
  IF trunc(vi_end_date) in ( trunc(vi_start_date), trunc(vi_start_date) + 1 ) then
    return 1; -- TRUE
  -- An end day before start day is invalid
  elsif trunc(vi_end_date) < trunc(vi_start_date) then
    return 0; -- FALSE
  end if;

  -- Now loop through the days between first and last to look out for gaps, i.e. skipped working days
  for offset in 1 .. trunc(vi_end_date) - trunc(vi_start_date) - 1 loop
    -- If Saturday or Sunday, then we are fine with this, otherwise let's see if it's a holiday
    if to_char(trunc(vi_start_date) + offset, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') not in ('SAT','SUN') then
      -- If it's neither Saturday or Sunday nor a holiday then return false
      select count(*) into v_count from temp_holidys where holiday = trunc(vi_start_date) + offset;
      if v_count = 0 then 
        return 0; -- FALSE
      end if;
    end if;
  end loop;

  -- No gap detected; return true
  return 1; -- TRUE
end;

这是select语句。在有序列表中,它首先查找组更改,即用户更改或日期不被视为相邻。基于这些组构建,最后我们可以找到每组的第一个和最后一个日期。

select user_id, min(work_date), max(work_date)
from
(
  select user_id, work_date, sum(group_change) over(order by user_id, work_date) as date_group
  from
  (
    select
      user_id, 
      work_date, 
      case when
        user_id  nvl(lag(user_id) over(order by user_id, work_date), user_id) or
        are_dates_adjacent(nvl(lag(work_date) over(order by user_id, work_date), work_date), work_date) = 0 
      then 1 else 0 end as group_change
    from Test_Seq
    order by user_id, work_date
  )
)
group by user_id, date_group
order by user_id, min(work_date);

编辑:这是select语句,只为您提供一个用户的最后工作时间。

select start_date, end_date
from
(
    select min(work_date) as start_date, max(work_date) as end_date
    from
    (
        select work_date, sum(group_change) over(order by work_date) as date_group
        from
        (
            select
                work_date, 
                case when
                    are_dates_adjacent(nvl(lag(work_date) over(order by work_date), work_date), work_date) = 0 
                then 1 else 0 end as group_change
            from Test_Seq
            where user_id = 1
            order by work_date
        )
    )
    group by date_group
    order by min(work_date) desc
)
where rownum = 1;

答案 1 :(得分:0)

这是一种方法。

  • 在这种方法中,所有工作日,假日和周末都放在同一张表中。
  • 然后确定每个序列的开始,并按降序排列日期。
  • 每个序列都有一个数字。
  • 找出第一个序列的最大值和最小值,这是必需的结果。

以下是用户1的查询。

/*---for user 1---*/
with minmaxdays as(
  --find the latest and earliest working date for each user
  select min(work_date) min_date,
         max(work_date) max_date
  from test_seq
  where user_id = 1
  ),
alldays as(
  --generate all days from earliest to latest dates
  select min_date + level all_days
  from minmaxdays
  connect by min_date + level < max_date
  ),
combined_test_seq as(
  --get the working days
  select work_date working_days, 'W' date_type  --W indicates working days
  from test_seq
  where user_id = 1
  union all
  --get the holidays
  select holiday working_days, 'H' date_type   --H indicates holidays/weekends
  from temp_holidys
  union all
  --get all the weeknds
  select all_days working_days, 'H' date_type   --H indicates holidays/weekends
  from alldays
  where to_char(all_days,'D') in ('1','7')      --select only saturdays and sundays
  ),
grouping as(
--find out the beginning of each sequence
  select working_days,
         date_type,
         case when working_days + 1 = 
                   lag(working_days,1) over (order by working_days desc)
              then 0
              else 1
         end seq_start
  from combined_test_seq
  ),
grouping2 as(
--assign sequence no, and keep only the working days
  select working_days,
         sum(seq_start) over (order by working_days desc) grp
  from grouping
  where date_type = 'W'
  )
-- get the max and min date in the first sequence.
select max(working_days) keep (dense_rank first order by grp),
       min(working_days) keep (dense_rank first order by grp)
from grouping2;

结果:

max(date)       min(date)
-------------------------
10-SEP-2013     06-SEP-2013

演示here

答案 2 :(得分:-1)

您能否显示所需的输出?这会有所帮助。

我试图理解你的查询并想出了这个 -

select distinct * from 
(
select user_id, min(work_date) over (partition by user_id) start_date, max(work_date) over (partition by user_id) end_date  
from test_seq t
where not exists (select null from temp_holidys h
                         where h.holiday=t.work_date)
)

谢谢,Aditya