2010-11-11 9 views
5

Estoy usando Oracle PL/SQL.En PL/SQL, ¿cómo se actualiza una fila según la siguiente fila?

Tengo una tabla con sello de tiempo T, y quiero establecer el valor de una fila para que la columna A sea la misma que la de la fila anterior, si están ordenadas por columnas B y Timestamp, siempre que las marcas de tiempo no sean diferente por más de 45 segundos.

En pseudocódigo, que es algo así como:

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 
    ) 

He intentado esto:

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 
    ) 

Pero tengo:

error (38,16): PL/SQL: ORA-00934: la función de grupo no está permitida aquí

apuntando a la primera instancia de RANK.

¿Qué hice mal y cómo lo hago bien?

+1

Estaba a punto de sugerir usar 'lag' o' lead', pero eso podría no funcionar tampoco ... o podría intentar 'update T set a = seleccionar Q1.A from ((seleccionar A, rownum r1 de T) Q1 left outer join (seleccione A, rownum r2 de T) Q2 en Q1.r1 = Q2.r2-1) ' – FrustratedWithFormsDesigner

+0

@FrustratedWithFormsDesigner - Tiene razón en que el retraso y el plomo me dan el mismo problema. Sin embargo, tengo algo basado en tu otra sugerencia para compilar, ¡así que gracias! Si quieres copiarlo en una respuesta para que lo acepte, estaría bien. –

+0

¡Hecho! (Publiqué como comentario al principio porque nunca he intentado esto para una actualización y no estaba seguro de que funcionaría);) – FrustratedWithFormsDesigner

Respuesta

3

Intenta usar una declaración de fusión. No estoy seguro de que haga lo que quiere, pero debería funcionar. Lamentablemente, la cláusula de inserción es necesaria) pero nunca se debe invocar.

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

Gracias! Tengo algo basado en esto para compilar, y tiene sentido para mí que funcione. –

+1

Todavía creo que es posible que tengas que mirar sus requisitos. ¿Qué sucede cuando tiene varias transacciones todas dentro de 45 segundos el uno del otro? Por ejemplo, tres filas con 40 segundos de diferencia? –

1

Se puede tratar algo como esto:

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

Siento que eso podría causar problemas de rendimiento, ¿no? Estoy lidiando con decenas de miles de filas. –

+1

@ MOE37x3 No asumiría nada ... Dependiendo de con quién hables, decenas de miles de filas realmente no es mucho. – Fosco

0

usted podría intentar (podría necesitar algunos ajustes para hacerlo bien, pero la idea es dos sub consultas ordenadas idénticas unidas por desplazamiento 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

Parece que rownum no toma el orden en cuenta. Simplemente lo hace por el orden en que se accedió a las filas. http://www.dbforums.com/oracle/988716-rownum-order.html –

+0

@ MOE37x3: Lo sé, mi primera no tenía ningún pedido. Agregué ordenamiento en B y el campo de marca de tiempo. Eso funciona para ti? – FrustratedWithFormsDesigner

+0

De acuerdo con lo que he leído, rownum se aplica antes del pedido, por lo que los números estarán allí, y las filas estarán en el orden especificado, pero los números podrían estar en otro orden. –

1

Y otra opción ... no acaba de hacer lo que no quiere, porque no tiene en cuenta el requisito para ordenar B pero podría darle algo en que pensar .... Sin definiciones de tabla y cosas que era un poco difícil de manejar exactamente lo que se requería.

Editar: al leer la pregunta de nuevo, parece que su sintaxis es incorrecta. Las funciones de grupo (lead/lag/rank, etc.) solo pueden aparecer en la lista de selección o en la cláusula order by. Se evalúan después de las uniones, dónde, agrupar por y tener cláusulas. Entonces algo como lo que se muestra a continuación debería funcionar.

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

Gracias por explicar el error en mi código. –

0

Lo que puedes hacer es.

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 

Esto requiere que la columna que desea actualizar sea nula, a menos que desee actualizarlas todas.

Cuestiones relacionadas