2015-01-08 5 views
7

Ich bin auf der Suche nach einer SQL-Abfrage, die die längste Zeit identifiziert, die eine Person gegangen ist, ohne eine Fleischmahlzeit zu haben. Im Idealfall würde sich die Ausgabe wieIdentifizieren eines Zeitraums mit bestimmten Eigenschaften mit SQL

person periodstart periodend 

wo für jede Person ohne Fleisch die längste Periode identifiziert werden würde und

periodstart würde die Zeit des ersten Nicht-Fleischmehl sein

periode wäre die Zeit der ersten Fleischmahlzeit nach.

SQL erstellt unten Tabelle und Daten.

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

commit; 
+4

Plus eins für das Hinzufügen von ddl und dml. Dies ist leider nur selten auf SO zu sehen. –

+1

Ein guter Weg, um eine Frage zu stellen, endlich nach langer Zeit gesehen! –

Antwort

2

Dies ist ein Lücken-und-Inseln Problem verstehen können, und es gibt verschiedene Möglichkeiten, sich ihm zu nähern. Eine davon ist an analytic function effect/trick zu verwenden, um die Ketten von zusammenhängenden Zeiträumen findet für jeden Typ:

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; 

Die ‚Kette‘ Pseudo auf einem case hier basiert, wie Sie Obst und Gemüse wollen - oder irgendetwas nicht-Fleisch - behandelt die gleich.

Anschließend können Sie das als eine innere Abfrage verwenden den Start jedes Fleisch und nicht-Fleisch Zeitraum von der ersten Mahlzeit in jeder Kette zu finden:

select person, meat, min(mealtime) as first_meal 
from (
    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 
) 
group by person, meat, 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 
... 

Sie wollen die Zeit der ersten nicht zur Deckung - Fleisch-Mahlzeit zum nächsten Fleisch-Mahlzeit, so können Sie verwenden, dass als eine innere Abfrage mit Blei und Lag, um die Zeilen auf beiden Seiten zu spähen: für eine Gemüse-Periode Sie spähen voraus, um den Beginn der nächsten Fleischperiode zu sehen; für einen Fleisch Zeitraum spähen Sie zurück, den er von der vorherigen veg Zeit beginnen, um zu sehen:

select person, meat, 
    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, meat, min(mealtime) as first_meal 
    from (
    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 
) 
    group by person, meat, 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 
... 

Dass Sie Duplikate gibt, effektiv, obwohl ich das ‚Fleisch‘ der Flagge in ihm auf dies ein wenig klarer zu machen verlassen haben Punkt. Vorausgesetzt, dass Sie die neueste offene Zeit ignorieren wollen Sie nur diejenigen überspringen müssen und die Beseitigung der Duplikate:

select person, period_start, period_end 
from (
    select person, meat, 
    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, meat, min(mealtime) as first_meal 
    from (
     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 
    ) 
    group by person, meat, 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 mit den Zwischenschritten in vollem Umfang.

Verspätet realisiert man nur die längste Zeit für jede Person wollte, die Sie mit einer weiteren Schicht zu bekommen:

select person, period_start, period_end 
from (
    select person, period_start, 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.

1

Lösung ist in SQL Server ich hoffe, dass Sie leicht

with x as (
select ROW_NUMBER()over(Partition by person order by MealTime) rowId,* from #MEALS 
) 
,y as (
select ROW_NUMBER() over(Partition by person order by MealTime) rowID, * from 
#MEALS where FOODTYPE='meat') 
select x.PERSON,x.MEALTIME startdate,y.MEALTIME endDate,  datediff(second,x.MEALTIME,y.MEALTIME) diff from x 
left join 
y on x.PERSON=y.PERSON where 
x.rowId=1 and y.rowID=1 
+0

Ich hoffe, ich habe korrekt zu Oracle konvertiert. Wenn ich habe, bekomme ich nicht die richtige Antwort! Es scheint, dass die erste fleischfreie Zeit nicht die längste Zeit ist.So bekomme ich Jane \t 04/01/2015 06:09:09 \t 07/01/2015 06:01:24 John \t 02/01/2015 10:03:23 \t 03/01/2015 10:03:23 Mary \t 02.01.2015 05:01:54 \t 03/01/2015 06:04:25 für John ist dies die erste Periode, aber nicht die längste. –

Verwandte Themen