¿Puede una solución T-SQL para huecos e islas ejecutarse más rápido que una solución C # que se ejecuta en el cliente?
Para ser específicos, proporcionemos algunos datos de prueba:
CREATE TABLE dbo.Numbers
(
n INT NOT NULL
PRIMARY KEY
) ;
GO
INSERT INTO dbo.Numbers
( n )
VALUES ( 1 ) ;
GO
DECLARE @i INT ;
SET @i = 0 ;
WHILE @i < 21
BEGIN
INSERT INTO dbo.Numbers
( n
)
SELECT n + POWER(2, @i)
FROM dbo.Numbers ;
SET @i = @i + 1 ;
END ;
GO
CREATE TABLE dbo.Tasks
(
StartedAt SMALLDATETIME NOT NULL ,
FinishedAt SMALLDATETIME NOT NULL ,
CONSTRAINT PK_Tasks PRIMARY KEY ( StartedAt, FinishedAt ) ,
CONSTRAINT UNQ_Tasks UNIQUE ( FinishedAt, StartedAt )
) ;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, n, '20100101') AS StartedAt ,
DATEADD(MINUTE, n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Este primer conjunto de datos de prueba tiene exactamente una brecha:
SELECT StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE StartedAt BETWEEN DATEADD(MINUTE, 499999, '20100101')
AND DATEADD(MINUTE, 500006, '20100101')
El segundo conjunto de datos de prueba tiene espacios de 2M -1, un espacio entre cada dos intervalos adyacentes:
TRUNCATE TABLE dbo.Tasks;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, 3*n, '20100101') AS StartedAt ,
DATEADD(MINUTE, 3*n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Actualmente estoy ejecutando 2008 R2, pero las soluciones de 2012 son muy bienvenidas. He publicado mi solución C # como respuesta.
El siguiente código C # resuelve el problema:
Este código invoca este procedimiento almacenado:
Encuentra e imprime un espacio en intervalos de 2M en los siguientes tiempos, caché cálido:
Encuentra e imprime espacios de 2M-1 en intervalos de 2M en los siguientes tiempos, caché cálido:
Esta es una solución muy simple: me llevó 10 minutos desarrollarla. Un recién graduado de la universidad puede pensarlo. En el lado de la base de datos, el plan de ejecución es una combinación trivial de fusión que utiliza muy poca CPU y memoria.
Editar: para ser realista, estoy ejecutando cliente y servidor en cajas separadas.
fuente
Creo que he agotado los límites de mi conocimiento en el servidor SQL en este ...
Para encontrar una brecha en el servidor SQL (lo que hace el código C #), y no le importa comenzar o terminar las brechas (las que se encuentran antes del primer inicio o después del último final), la siguiente consulta (o variantes) es más rápido que pude encontrar:
Lo que funciona, aunque con poca mano, para cada conjunto de inicio-fin, puede tratar el inicio y el final como secuencias separadas, compensar el final en uno y se muestran los espacios.
Por ejemplo, tome (S1, F1), (S2, F2), (S3, F3) y ordene como: {S1, S2, S3, nulo} y {nulo, F1, F2, F3} Luego compare la fila n con la fila n en cada conjunto, y las brechas son donde el valor del conjunto F es menor que el valor del conjunto S ... el problema creo que es que en el servidor SQL no hay forma de unir o comparar dos conjuntos separados únicamente en el orden de los valores en el conjunto ... de ahí el uso de la función row_number para permitirnos fusionarnos basados únicamente en el número de fila ... pero no hay forma de decirle al servidor SQL que estos valores son únicos (sin insertarlos en una tabla var con un índice en él, lo que lleva más tiempo, lo probé), ¿así que creo que la combinación de combinación es menos que óptima? (aunque difícil de probar cuando es más rápido que cualquier otra cosa que pueda hacer)
Pude obtener soluciones usando las funciones LAG / LEAD:
(que por cierto, no garantizo los resultados, parece funcionar, pero creo que depende de que StartedAt esté en orden en la tabla Tareas ... y fue más lento)
Usando cambio de suma:
(no es de extrañar, también más lento)
Incluso probé una función agregada CLR (para reemplazar la suma; era más lenta que la suma y dependía de row_number () para mantener el orden de los datos), y CLR una función con valores de tabla (para abrir dos conjuntos de resultados y comparar valores basados puramente en secuencia) ... y también fue más lento. Me golpeé la cabeza muchas veces con las limitaciones de SQL y CLR, probando muchos otros métodos ...
¿Y para qué?
Al ejecutarse en la misma máquina y escupir tanto los datos de C # como los datos filtrados por SQL en un archivo (según el código de C # original), los tiempos son prácticamente los mismos ... aproximadamente 2 segundos para los datos de 1 gap (C # generalmente más rápido ), 8-10 segundos para el conjunto de datos de espacio múltiple (SQL generalmente más rápido)
NOTA : No utilice el entorno de desarrollo de SQL Server para la comparación de tiempos, ya que su visualización en la cuadrícula lleva tiempo. Según lo probado con SQL 2012, VS2010, .net 4.0 Perfil del cliente
Señalaré que ambas soluciones realizan más o menos la misma clasificación de datos en el servidor SQL, por lo que la carga del servidor para fetch-sort será similar, cualquiera que sea la solución que use, la única diferencia es el procesamiento en el cliente (en lugar del servidor) y la transferencia a través de la red.
No sé cuál podría ser la diferencia al dividir por diferentes miembros del personal, tal vez, o cuando pueda necesitar datos adicionales con la información de brecha (aunque no puedo pensar en otra cosa que no sea una identificación del personal), o por supuesto si hay una conexión de datos lenta entre el servidor SQL y la máquina del cliente (o un cliente lento ) ... Tampoco he hecho una comparación de los tiempos de bloqueo o problemas de contención, o problemas de CPU / RED para múltiples usuarios ... Entonces No sé cuál es más probable que sea un cuello de botella en este caso.
Lo que sí sé es que sí, el servidor SQL no es bueno en este tipo de comparaciones de conjuntos, y si no escribe la consulta correctamente, la pagará caro.
¿Es más fácil o más difícil que escribir la versión de C #? No estoy completamente seguro, el cambio +/- 1, ejecutar la solución total tampoco es del todo intuitivo, y yo, pero no es la primera solución a la que llegaría un graduado promedio ... una vez hecho, es bastante fácil de copiar, pero se necesita una idea para escribir en primer lugar ... lo mismo se puede decir de la versión SQL. ¿Qué es más difícil? ¿Cuál es más robusto para rogue datos? ¿Cuál tiene más potencial para operaciones paralelas? ¿Realmente importa cuando la diferencia es tan pequeña en comparación con el esfuerzo de programación?
Una última nota; hay una restricción no declarada en los datos: StartedAt debe ser menor que FinishedAt o obtendrá malos resultados.
fuente
Aquí hay una solución que se ejecuta en 4 segundos.
fuente