2009-04-02 35 views
55

Necesito calcular la diferencia de una columna entre dos líneas de una tabla, ¿hay alguna manera de hacerlo directamente en SQL? Estoy usando Microsoft SQL Server 2008.¿Hay alguna manera de acceder al valor de "fila anterior" en una instrucción SELECT?

Busco algo como esto:

SELECT value - (previous.value) FROM table 

Imaginando que el "anterior" de referencia variable la última fila seleccionada. Por supuesto que con una selección como esta voy a terminar con n-1 filas seleccionadas en una tabla con n filas, eso no es probable, en realidad es exactamente lo que necesito.

¿Es posible de alguna manera?

+5

Bueno, solo agreguemos un comentario útil para los nuevos espectadores. SQL 2012 tiene LAG y LEAD ahora :) Consulte este enlace http://blog.sqlauthority.com/2013/09/22/sql-server-how-to-access-the-previous-row-and-next-row- value-in-select-statement/ –

Respuesta

39

SQL no se ha incorporado en la noción de orden, por lo que necesita para ordenar por alguna columna para que esto tenga sentido. Algo como esto:

select t1.value - t2.value from table t1, table t2 
where t1.primaryKey = t2.primaryKey - 1 

Si sabe cómo ordenar las cosas, pero no la forma de obtener el valor anterior dada la actual (EG, que desea ordenar alfabéticamente) entonces yo no sé de una manera de hacer eso en SQL estándar, pero la mayoría de las implementaciones SQL tendrán extensiones para hacerlo.

Aquí es una manera para que el servidor SQL que funciona si usted puede ordenar filas de modo que cada uno es distinto:

select rank() OVER (ORDER BY id) as 'Rank', value into temp1 from t 

select t1.value - t2.value from temp1 t1, temp1 t2 
where t1.Rank = t2.Rank - 1 

drop table temp1 

Si necesita romper los lazos, se puede añadir tantas columnas como sea necesario para la ORDEN POR.

+0

Está bien, el orden no es un problema, simplemente lo eliminé del ejemplo para hacerlo más simple, voy a intentarlo. –

+4

que asume que las claves primarias se generan secuencialmente y las filas nunca se eliminan y la selección no tiene ninguna otra cláusula de orden yyy ... – MartinStettner

+0

Martin es correcto. Aunque esto podría funcionar en algunos casos, realmente necesita definir exactamente lo que quiere decir con "anterior" en un sentido comercial, preferiblemente sin depender de un ID generado. –

8

combinación izquierda de la mesa para sí mismo, con la condición de unión funcionó por lo que la fila coincidente en la versión conjunta de la mesa es una fila anterior, para su definición particular de "anterior".

Actualización: Al principio pensaba que se quiere mantener todas las filas, con nulos para la condición en la que no había fila anterior. Al leerlo de nuevo, solo quiere que las filas se seleccionen, por lo que debería unirse internamente en lugar de unirse a la izquierda.


Actualizar:

las nuevas versiones de SQL Server también tienen al sistema de ventanas LEAD GAL y que se puede utilizar para esto, también.

24

Oracle, PostgreSQL, SQL Server y muchos más motores RDBMS tienen funciones analíticas llamadas LAG y LEAD que hacen esto mismo.

En SQL Server anteriores a 2012 que había necesidad de hacer lo siguiente:

SELECT value - (
     SELECT TOP 1 value 
     FROM mytable m2 
     WHERE m2.col1 < m1.col1 OR (m2.col1 = m1.col1 AND m2.pk < m1.pk) 
     ORDER BY 
       col1, pk 
     ) 
FROM mytable m1 
ORDER BY 
     col1, pk 

, donde COL1 es la columna que está pidiendo por.

Tener un índice en (COL1, PK) mejorará en gran medida esta consulta.

+13

SQL Server 2012 ahora también tiene LAG y LEAD. – ErikE

+0

La secuencia de comandos Hana SQL también admite LAG y LEAD. – mik

+0

Solo para agregar otro comentario a los espectadores que llegaron aquí buscando hacerlo en Hive. También tiene funciones LAG y LEAD. Documentación aquí: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+WindowingAndAnalytics –

2

La respuesta seleccionada solo funcionará si no hay lagunas en la secuencia. Sin embargo, si está utilizando una identificación autogenerada, es probable que haya lagunas en la secuencia debido a las inserciones que se retrotraeron.

Este método debería funcionar si tiene huecos

declare @temp (value int, primaryKey int, tempid int identity) 
insert value, primarykey from mytable order by primarykey 

select t1.value - t2.value from @temp t1 
join @temp t2 
on t1.tempid = t2.tempid - 1 
23
WITH CTE AS (
    SELECT 
    rownum = ROW_NUMBER() OVER (ORDER BY columns_to_order_by), 
    value 
    FROM table 
) 
SELECT 
    curr.value - prev.value 
FROM CTE cur 
INNER JOIN CTE prev on prev.rownum = cur.rownum - 1 
+0

Funciona correctamente si no hay agrupamiento en la consulta, pero ¿qué sucede si queremos restar valores del valor anterior solo dentro de un grupo? , digamos mismo ID de empleado, ¿cómo podemos hacer eso? Coz ejecuta esto solo funciona para las 2 filas superiores de cada grupo y no para el resto de las filas en ese grupo. Para esto, utilicé este código en while loop, pero parece ser muy lento. ¿Algún otro enfoque que pudiéramos en este escenario? ¿Y eso también solo en SQL Server 2008? –

2
select t2.col from (
select col,MAX(ID) id from 
(
select ROW_NUMBER() over(PARTITION by col order by col) id ,col from testtab t1) as t1 
group by col) as t2 
26

Utilice la función lag:

SELECT value - lag(value) OVER (ORDER BY Id) FROM table 

secuencias utilizadas para los ID puede omitir valores, por lo Id-1 hace no siempre funciona

+1

Esta es la solución PostgreSQL. La pregunta es sobre MSSQL. MSSQL tiene dicha función en las versiones 2012+ (https://msdn.microsoft.com/en-us/en-en/library/hh231256(v=sql.120).aspx) – Kromster

+2

@KromStern No solo en la solución PostgreSQL. [Funciones de la ventana SQL] (https://www.simple-talk.com/sql/t-sql-programming/window-functions-in-sql/) se introdujeron en [SQL: 2003] (https: // en. wikipedia.org/wiki/SQL:2003) estándar. –

+1

En otras palabras, ¿funcionará esto en caso de OP? – Kromster

Cuestiones relacionadas