2012-06-20 14 views
21

Digamos que tengo el siguiente cuadro:seleccionar las filas donde el valor de la columna ha cambiado

Value Time 
0  15/06/2012 8:03:43 PM 
1  15/06/2012 8:03:43 PM  * 
1  15/06/2012 8:03:48 PM 
1  15/06/2012 8:03:53 PM 
1  15/06/2012 8:03:58 PM  
2  15/06/2012 8:04:03 PM  * 
2  15/06/2012 8:04:08 PM 
3  15/06/2012 8:04:13 PM  * 
3  15/06/2012 8:04:18 PM 
3  15/06/2012 8:04:23 PM 
2  15/06/2012 8:04:28 PM  * 
2  15/06/2012 8:04:33 PM  

¿Cómo selecciono las filas con estrellas, es decir, donde Value ha cambiado? Básicamente, estoy tratando de encontrar el momento en que Value ha cambiado para poder hacer otras consultas basadas en esos intervalos de tiempo. La solución no debe depender de saber Value o Time de antemano.

Me parece que esto no debería ser muy difícil (¡pero al parecer es lo suficientemente difícil para mí!).

Actualmente estoy usando SQL Server 2008 aunque tengo acceso a 2012 si las nuevas funciones de ventana/analíticas son útiles.

Intenté adaptar las soluciones aquí http://blog.sqlauthority.com/2011/11/24/sql-server-solution-to-puzzle-simulate-lead-and-lag-without-using-sql-server-2012-analytic-function/ pero mi consulta no se completó después de una hora. Creo que las uniones explotan el tamaño de la fila a algo inmanejable (o lo arruiné).

Puedo resolver este problema con el código C# y múltiples llamadas a bases de datos, pero parece que se podría hacer algo en una función con valores de tabla o SP que sería mucho mejor.

Además, una solución que solo funciona con aumentar Value está bien si es más fácil.

Respuesta

27

creo que esto es lo que está buscando:

;WITH x AS 
(
    SELECT value, time, rn = ROW_NUMBER() OVER 
    (PARTITION BY Value ORDER BY Time) 
    FROM dbo.table 
) 
SELECT * FROM x WHERE rn = 1; 

Esto puede ser lento si el conjunto de resultados es grande y no hay un índice de apoyo a la buena ...

EDITAR

Ah, espere un segundo, los valores suben y bajan, no solo hacia arriba ... si ese es el caso, puede intentar este enfoque mucho más lento:

DECLARE @x TABLE(value INT, [time] DATETIME) 

INSERT @x VALUES 
(0,'20120615 8:03:43 PM'),-- 
(1,'20120615 8:03:43 PM'),--* 
(1,'20120615 8:03:48 PM'),-- 
(1,'20120615 8:03:53 PM'),-- 
(1,'20120615 8:03:58 PM'),-- 
(2,'20120615 8:04:03 PM'),--* 
(2,'20120615 8:04:08 PM'),-- 
(3,'20120615 8:04:13 PM'),--* 
(3,'20120615 8:04:18 PM'),-- 
(3,'20120615 8:04:23 PM'),-- 
(2,'20120615 8:04:28 PM'),--* 
(2,'20120615 8:04:33 PM'); 

;WITH x AS 
(
    SELECT *, rn = ROW_NUMBER() OVER (ORDER BY time) 
    FROM @x 
) 
SELECT x.value, x.[time] 
FROM x LEFT OUTER JOIN x AS y 
ON x.rn = y.rn + 1 
AND x.value <> y.value 
WHERE y.value IS NOT NULL; 

Resultados:

value time 
----- ----------------------- 
1  2012-06-15 20:03:43.000 
2  2012-06-15 20:04:03.000 
3  2012-06-15 20:04:13.000 
2  2012-06-15 20:04:28.000 
+0

Wow that fue rápido. El aumento de valor está bien. Lo intentaré. – agentnega

+0

Fantástico, gracias @ Aaron Bertrand. En mis datos, la segunda versión tomó menos del doble de tiempo que la primera versión, por lo que la diferencia de rendimiento no es tan mala. – agentnega

+0

¿Cómo convertir esta consulta para incluir también una ID (nueva columna) en el resultado resultante? – ThinkCode

12
DECLARE @x TABLE(value INT, [time] DATETIME) 

INSERT @x VALUES 
(0,'20120615 8:03:43 PM'),-- 
(1,'20120615 8:03:43 PM'),--* 
(1,'20120615 8:03:48 PM'),-- 
(1,'20120615 8:03:53 PM'),-- 
(1,'20120615 8:03:58 PM'),-- 
(2,'20120615 8:04:03 PM'),--* 
(2,'20120615 8:04:08 PM'),-- 
(3,'20120615 8:04:13 PM'),--* 
(3,'20120615 8:04:18 PM'),-- 
(3,'20120615 8:04:23 PM'),-- 
(2,'20120615 8:04:28 PM'),--* 
(2,'20120615 8:04:33 PM'); 


; with temp as 
(
SELECT 
    value, [time], lag(value,1,-1) over (order by [time]) as lastValue 
FROM @x 
) 
SELECT 
    [value],[time] 
FROM 
    temp 
WHERE value <> lastValue 

Resultados:

value time 
--------------------------- 
0 2012-06-15 20:03:43.000 
1 2012-06-15 20:03:43.000 
2 2012-06-15 20:04:03.000 
3 2012-06-15 20:04:13.000 
2 2012-06-15 20:04:28.000 
+3

Limpio, pero vale la pena mencionar que esto solo es compatible con SQL 2012 en adelante. –

+0

¡Esto fue super rápido! La consulta de versiones anteriores funcionó durante 11 minutos y luego tuve que cancelarla. –

1

Podemos hacer esto utilizando sub consultas también

SELECT sub1.value, sub1.time FROM 
    (SELECT *,rn,id FROM 
    (SELECT *,row_number() over (partition by value order by time) AS rn, row_number() over (order by time) AS id FROM x) order by time) sub1 
    LEFT OUTER JOIN 
    (SELECT *,rn,id FROM 
    (SELECT *,row_number() over (partition by value order by time) AS rn, row_number() over (order by time) AS id FROM x) order by time) sub2 
    ON sub1.id = sub2.id + 1 
    WHERE sub1.rn - sub2.rn <> 1 OR sub2.rn IS NULL; 

lo tanto, me han comparado los valores de 2 filas si cambia, la diferencia de r no será igual a 1 de lo contrario rn el valor se incrementará en 1, así que seleccioné todas las filas cuya diferencia con el valor rn de la siguiente fila no es 1 y sub2.rn IS NULL se usa para la primera fila porque la unión ocurrirá desde id = 2.

Cuestiones relacionadas