使用sql识别具有特定特征的时段

我正在寻找一个SQL查询,它可以确定一个人在没有吃饭的情况下最长的一段时间.理想情况下,输出看起来像
person  periodstart  periodend

每个人在哪里可以确定没有肉的最长时期

periodstart would be the time of the first non-meat meal

periodend would be the time of the first meat meal following.

下面的sql创建表和数据.

CREATE TABLE MEALS 
(
  PERSON VARCHAR2(20 BYTE),MEALTIME DATE,FOODTYPE VARCHAR2(20) 
);

Insert into MEALS (PERSON,MEALTIME,FOODTYPE) 
values ('Jane',to_date('04-JAN-15 06:09:09','DD-MON-RR HH24:MI:SS'),'fruit');
Insert into MEALS (PERSON,to_date('05-JAN-15 06:09:09','veg');
Insert into MEALS (PERSON,to_date('07-JAN-15 06:01:24','meat');
Insert into MEALS (PERSON,to_date('07-JAN-15 12:03:50',FOODTYPE) 
values ('John',to_date('02-JAN-15 10:03:23',to_date('03-JAN-15 10:03:23',to_date('04-JAN-15 10:03:23',to_date('05-JAN-15 07:03:23',to_date('05-JAN-15 10:03:23',to_date('06-JAN-15 05:01:54',to_date('06-JAN-15 10:03:23',FOODTYPE) 
values ('Mary',to_date('02-JAN-15 05:01:54',to_date('03-JAN-15 06:04:25',to_date('05-JAN-15 04:04:25',to_date('05-JAN-15 06:04:25',to_date('07-JAN-15 06:04:25','veg');

commit;

解决方法

这是一个间隙和岛屿问题,有各种方法来处理它.一种是使用 an analytic function effect/trick来查找每种类型的连续周期链:
select person,mealtime,foodtype,case when foodtype = 'meat' then 'Yes' else 'No' end as meat,dense_rank() over (partition by person,case when foodtype = 'meat' then 1 else 0 end order by mealtime)
    - dense_rank() over (partition by person order by mealtime) as chain
from meals
order by person,mealtime;

‘链’假柱是基于这里的一个案例,因为你想要水果和蔬菜 – 或任何非肉类 – 处理相同.

然后,您可以使用它作为内部查询,从每个链中的第一餐开始查找每个肉类和非肉类时期的开始:

select person,meat,min(mealtime) as first_meal
from (
  select person,case when foodtype = 'meat' then 1 else 0 end order by mealtime)
      - dense_rank() over (partition by person order by mealtime) as chain
  from meals
)
group by person,chain
order by person,min(mealtime);

PERSON               MEAT FirsT_MEAL       
-------------------- ---- ------------------
Jane                 No   04-JAN-15 06:09:09 
Jane                 Yes  07-JAN-15 06:01:24 
Jane                 No   07-JAN-15 12:03:50 
John                 No   02-JAN-15 10:03:23 
...

你希望这段时间能够覆盖第一顿非肉食到下一顿肉餐,所以你可以把它作为一个带有超前和滞后的内部查询来偷看任何一侧的行:对于一个蔬菜时期,你向前看,看看开始下一个肉食期;在肉食期间,你会看到他开始上一个蔬菜时期:

select person,case when meat = 'Yes' then lag(first_meal) over (partition by person
      order by first_meal) else first_meal end as period_start,case when meat = 'No' then lead(first_meal) over (partition by person
      order by first_meal) else first_meal end as period_end
from (
  select person,min(mealtime) as first_meal
  from (
    select person,case when foodtype = 'meat' then 1 else 0 end order by mealtime)
        - dense_rank() over (partition by person order by mealtime) as chain
    from meals
  )
  group by person,chain
)
order by person,period_start;

PERSON               MEAT PERIOD_START       PERIOD_END       
-------------------- ---- ------------------ ------------------
Jane                 No   04-JAN-15 06:09:09 07-JAN-15 06:01:24 
Jane                 Yes  04-JAN-15 06:09:09 07-JAN-15 06:01:24 
Jane                 No   07-JAN-15 12:03:50                    
John                 No   02-JAN-15 10:03:23 03-JAN-15 10:03:23 
...

这有效地给了你重复,虽然我已经留下了’肉’标志,以使它在这一点上更清晰.假设您想忽略最新的开放期间,您只需要跳过这些并消除重复:

select person,period_start,period_end
from (
  select person,case when meat = 'Yes' then lag(first_meal) over (partition by person
        order by first_meal) else first_meal end as period_start,case when meat = 'No' then lead(first_meal) over (partition by person
        order by first_meal) else first_meal end as period_end
  from (
    select person,min(mealtime) as first_meal
    from (
      select person,case when foodtype = 'meat' then 1 else 0 end order by mealtime)
          - dense_rank() over (partition by person order by mealtime) as chain
      from meals
    )
    group by person,chain
  )
)
where meat = 'No'
and period_start is not null
and period_end is not null
order by person,period_start;

PERSON               PERIOD_START       PERIOD_END       
-------------------- ------------------ ------------------
Jane                 04-JAN-15 06:09:09 07-JAN-15 06:01:24 
John                 02-JAN-15 10:03:23 03-JAN-15 10:03:23 
John                 04-JAN-15 10:03:23 06-JAN-15 10:03:23 
Mary                 02-JAN-15 05:01:54 03-JAN-15 06:04:25 
Mary                 05-JAN-15 04:04:25 05-JAN-15 06:04:25

SQL Fiddle中间步骤全部完成.

姗姗来迟地意识到你只想要每个人最长的时间,你可以用另一层来获得:

select person,period_end,rank() over (partition by person order by period_end - period_start desc) as rnk
  from (
    ...
  )
  where meat = 'No'
  and period_start is not null
  and period_end is not null
)
where rnk = 1
order by person,period_start;

PERSON               PERIOD_START       PERIOD_END       
-------------------- ------------------ ------------------
Jane                 04-JAN-15 06:09:09 07-JAN-15 06:01:24 
John                 04-JAN-15 10:03:23 06-JAN-15 10:03:23 
Mary                 02-JAN-15 05:01:54 03-JAN-15 06:04:25

Updated SQL Fiddle.

相关文章

SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_n...
if not exists(select name from syscolumns where name=&am...
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_n...
要在 SQL Server 2019 中设置定时自动重启,可以使用 Window...
您收到的错误消息表明数据库 'EastRiver' 的...
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标...