2010-11-11 12 views
5

Sto usando Oracle PL/SQL.In PL/SQL, come aggiorni una riga in base alla riga successiva?

Ho una tabella con data e ora T, e voglio impostare il valore di una riga per la colonna A per essere uguale a quello della riga precedente, se sono ordinati per le colonne B e Timestamp, a condizione che i timestamp non siano diverso da più di 45 secondi.

In pseudocodice, è qualcosa di simile:

UPDATE T t_curr 
    SET A = 
    (SELECT A 
     FROM T t_prev 
     INNER JOIN t_curr 
     ON (t_prev is the row right before t_curr, when you sort by B and Timestamp) 
      AND t_curr.Timestamp - t_prev.Timestamp < 45 
    ) 

ho provato questo:

UPDATE T t_curr 
    SET A = 
    (SELECT A 
     FROM T t_prev 
     INNER JOIN t_curr 
     ON RANK (t_curr) 
      OVER (B, Timestamp) 
      = 1 + RANK (t_prev) 
      OVER (B, Timestmap) 
      AND t_curr.Timestamp - t_prev.Timestamp < 45 
    ) 

Ma ho ottenuto:

errore (38,16): PL/SQL: ORA-00934: la funzione di gruppo non è consentita qui

puntato alla prima istanza di RANK.

Cosa ho fatto di sbagliato e come ottengo questo?

+1

Stavo per suggerire di usare 'lag' o' lead' ma potrebbe non funzionare neanche ... o potresti provare 'update T set a = seleziona Q1.A da ((seleziona A, rownum r1 da T) Q1 left outer join (selezionare A, rownum r2 da T) Q2 su Q1.r1 = Q2.r2-1) ' – FrustratedWithFormsDesigner

+0

@FrustratedWithFormsDesigner - Hai ragione che lag e lead mi fanno lo stesso problema. Ho ottenuto qualcosa in base al tuo altro suggerimento da compilare, però, quindi grazie! Se vuoi copiarlo in una risposta per accettarlo, andrebbe bene. –

+0

Fatto! (Ho postato come commento all'inizio perché non ho mai provato questo per un aggiornamento e non ero sicuro che avrebbe funzionato);) – FrustratedWithFormsDesigner

risposta

3

Provare a utilizzare un'istruzione di fusione. Non sono sicuro che faccia ciò che vuoi ma dovrebbe funzionare. Sfortunatamente la clausola di inserimento è necessaria) ma non dovrebbe mai essere chiamata.

merge into t a 
using (
    select 
    A, 
    B, 
    timestamp, 
    lag(A) over (order by id, timestamp) as prior_A, 
    lag(timestamp) over (order by B, timestamp) as prior_timestamp 
    from t) b 
on (a.B = b.B) 
when matched then 
    update set a.a = case when b.timestamp-b.prior_timestamp <= 45 
    then b.prior_A else b.A end 
when not matched then insert (B) values (null) 
+0

Grazie, ho ottenuto qualcosa basato su questo per compilare, e ha senso per me che dovrebbe funzionare. –

+1

Continuo a pensare che potrebbe essere necessario dare un'occhiata le tue esigenze Cosa succede quando hai diverse transazioni tutte entro 45 secondi l'una dall'altra? Ad esempio, tre righe distanti tra loro ogni 40 secondi? –

1

Potete provare qualcosa di simile:

update x 
set x = y.A 
from T x 
join T y 
where x.B = (select MAX(B) from T where B < y.B) 
and x.Timestamp = (select MAX(Timestamp) from T where Timestamp < y.Timestamp) 
and y.Timestamp - x.Timestamp < 45 
+0

Mi sembra che ciò potrebbe causare problemi di prestazioni, non è vero? Ho a che fare con decine di migliaia di righe. –

+1

@ MOE37x3 Non darei per scontato nulla ... A seconda di chi si parla, decine di migliaia di righe non sono poi così tanto. – Fosco

0

Si potrebbe provare (potrebbe essere necessario qualche ritocco per farlo bene, ma l'idea è di due sottointerrogazioni identici ordinate uniti da compensare rownumbers)

update T set a = (select A1 
       from (
         select S1.A A1, rownum r1 
         from (select * from T order by B, timestamp) S1 
         left outer join 
         select S2.A A2, rownum r2 
         from (select * from T order by B, timestamp) S2 
         on r1 = r2-1 
        ) 
       ) 
+0

Sembra che rownum non prenda l'ordine in considerazione. Fa solo dall'ordine in cui le righe sono state accedute. http://www.dbforums.com/oracle/988716-rownum-order.html –

+0

@ MOE37x3: lo so, il mio primo non ha avuto alcun ordine. Ho aggiunto l'ordine su B e il campo timestamp. Funziona per te? – FrustratedWithFormsDesigner

+0

Secondo quanto ho letto, rownum viene applicato prima dell'ordine, quindi i numeri saranno lì e le righe saranno nell'ordine specificato, ma i numeri potrebbero essere in qualche altro ordine. –

1

E un'altra opzione ... non abbastanza fare quello che voglio perché ignora l'obbligo di ordinare il B ma potrebbe dare qualcosa a cui pensare .... Senza definizioni di tabella e le cose che era un po 'difficile da gestire esattamente ciò che era necessario.

Modifica: alla lettura della domanda, sembra che la sintassi sia errata. Le funzioni di gruppo (lead/lag/rank ecc.) Possono apparire solo nell'elenco di selezione o nella clausola order by. Vengono valutati dopo i join, dove, raggruppando per e avendo clausole. Quindi qualcosa come quello mostrato qui sotto dovrebbe funzionare.

update T a 
set A = (select 
    new_A 
    from (
    select 
    B, 
    A, 
    timestamp, 
    first_value(A) 
     over (order by timestamp range between 45 preceding and current row) as new_A 
    from mike_temp_1 
) b where b.id = a.id) 
+0

Grazie per aver spiegato l'errore nel mio codice. –

0

Quello che puoi fare è.

update t 
set colToUpdate = nextValue 
from (
select A 
     ,B 
     ,C 
     ,(LEAD(B, 1, null) over (order by A)) as nextValue 
    FROM db.schema.table 
) as t 
    where colToUpdate is null 

Ciò richiede che la colonna che si desidera aggiornare sia nullo, a meno che non si desideri aggiornarli tutti.

Problemi correlati