2013-07-24 2 views
5

Ho il seguente set di dati.Funzioni analitiche Oracle - ripristino di una clausola finestra

create table t1 (
    dept number, 
    date1 date 
); 

Table created. 

insert into t1 values (100, '01-jan-2013'); 
insert into t1 values (100, '02-jan-2013'); 
insert into t1 values (200, '03-jan-2013'); 
insert into t1 values (100, '04-jan-2013'); 
commit; 

il mio obiettivo è quello di creare una colonna rango che azzera ogni volta che cambia il reparto. La colonna più vicina che posso usare per la clausola "partition by" è dept, ma non mi darà il risultato desiderato.

SQL> select * from t1; 

     DEPT DATE1 
---------- --------- 
     100 01-JAN-13 
     100 02-JAN-13 
     200 03-JAN-13 
     100 04-JAN-13 

select dept, 
     date1, 
     rank() Over (partition by dept order by date1) rnk 
from t1 
order by date1; 

     DEPT DATE1   RNK 
---------- --------- ---------- 
     100 01-JAN-13   1 
     100 02-JAN-13   2 
     200 03-JAN-13   1 
     100 04-JAN-13   3 

L'uscita desiderata è la seguente. L'ultimo rnk = 1 è perché il record di gennaio-04 è il primo record dopo la modifica.

 DEPT DATE1   RNK 
---------- --------- ---------- 
     100 01-JAN-13   1 
     100 02-JAN-13   2 
     200 03-JAN-13   1 
     100 04-JAN-13   1 <<<---------- 

Eventuali suggerimenti?

+1

+1. . . Includete il codice per testare realmente ciò che volete sapere. E prova i risultati. Posso invitare due volte? –

+0

Grazie Gordon. Questa è la mia solita lamentela con altre domande, seguirò meglio quello che predico :) –

risposta

4

Questo è un po 'complicato. Invece di utilizzare rank() o simili, utilizzare lag() per vedere quando qualcosa cambia. Quindi fai una somma cumulativa della bandiera.

select dept, date1, 
     CASE WHEN StartFlag = 0 THEN 1 
      ELSE 1+StartFlag+NVL(lag(StartFlag) over (order by date1),0) 
     END as rnk 
from (select t1.*, 
      (case when dept = lag(dept) over (order by date1) 
        then 1 
        else 0 
       end) as StartFlag 
     from t1 
    ) t1 
order by date1; 

Here è lo SQLFiddle.

EDIT:

Questa è la modifica di Gordon mia risposta. Ops. La query originale era il 90% del modo in cui ci sono. Ha identificato i gruppi dove i numeri dovrebbero aumentare, ma non ha assegnato i numeri all'interno dei gruppi. Vorrei farlo con un altro livello di row_number() come in:

select dept, date1, 
     row_number() over (partition by dept, grp order by date1) as rnk 
from (select dept, date1, startflag, 
      sum(StartFlag) over (partition by dept order by date1) as grp 
     from (select t1.*, 
        (case when dept = lag(dept) over (order by date1) 
         then 0 
         else 1 
        end) as StartFlag 
      from t1 
      ) t1 
    ) t1 
order by date1; 

Così, l'idea generale è la seguente. Prima usare lag() per determinare dove inizia un gruppo (ovvero, dove c'è un cambio di reparto da una data alla successiva). Quindi, assegnare un "id di gruppo" a questi, facendo una somma cumulativa. Questi sono i record che devono essere elencati. Il passaggio finale è quello di enumerarli utilizzando row_number().

+0

Per far funzionare il ritardo, ho rimosso la partizione tramite dept nella query interna e sto vedendo 0s e 1s come previsto, ma la somma (Flag) non dà il risultato finale. Ho creato un violino di sql qui, se questo aiuta. http://sqlfiddle.com/#!4/0a9fe/7 –

+0

Se aggiungi un ordine per data al risultato, vedrai che il rango non viene calcolato come desiderato. –

+0

Sulla strada giusta, penso che questo sia ciò che è necessario: http://www.sqlfiddle.com/#!4/fc339/14 –

2

Questo potrebbe essere stato un caso per la clausola model, ma sfortunatamente ha drammaticamente sottoperformato su una quantità significativa di righe rispetto alla query di Gordon.

select dept, date1, rank from t1 
model 
    dimension by (row_number() over(order by date1) as rn) 
    measures(1 as rank, dept, date1) 
    rules ( 
    rank[1] = 1, 
    rank[rn > 1] = 
    case dept[cv()] 
     when dept[cv()-1] then rank[cv()-1] + 1 
     else 1 
    end 
) 

http://www.sqlfiddle.com/#!4/fc339/132

+0

Grazie per avermelo ricordato, ho davvero bisogno di capire la clausola del modello e il suo utilizzo è buono. :) –

0

L'approccio è:

  1. Mark ciascuna riga con un 'numero di riga' e un flag 'cambiato'
  2. Calcolare il 'RNK' finale come differenza tra il "numero di riga" e il massimo numero di riga precedente che corrisponde a una riga "modificata".

Questo è simile alla risposta di Gordon, ma scritto utilizzando un CTE che trovo più facile da leggere.

with cte as (
    select dept, date1, 
     row_number() over (order by date1) as row, 
     case when dept = (lag(dept) over (order by date1)) then 0 else 1 end as changed 
    from t1 
) 
select dept, date1, 
    row - max(case when changed = 1 then row else 1 end) over (order by date1) + 1 as rnk 
from cte 
order by date1