Tengo la siguiente consulta, y debido a muchas SUM
llamadas a funciones, mi consulta se está ejecutando demasiado lento. Tengo muchos registros en mi base de datos y me gustaría obtener un informe del año actual y del año pasado (últimos 30 días, últimos 90 días y últimos 365 días) para cada uno:
SELECT
b.id as [ID]
,d.[Title] as [Title]
,e.Class as [Class]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Current - Last 30 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Current - Last 30 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Current - Last 90 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Current - Last 90 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Current - Last 365 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Current - Last 365 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-13,GETDATE()) and a.DateCol <= DATEADD(MONTH,-12,GETDATE()) THEN a.col1 ELSE 0 END) as [Last year - Last 30 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-13,GETDATE()) and a.DateCol <= DATEADD(MONTH,-12,GETDATE()) THEN a.col2 ELSE 0 END) as [Last year - Last 30 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-5,GETDATE()) and a.DateCol <= DATEADD(QUARTER,-4,GETDATE()) THEN a.col1 ELSE 0 END) as [Last year - Last 90 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-5,GETDATE()) and a.DateCol <= DATEADD(QUARTER,-4,GETDATE()) THEN a.col2 ELSE 0 END) as [Last year - Last 90 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-2,GETDATE()) and a.DateCol <= DATEADD(YEAR,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Last year - Last 365 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-2,GETDATE()) and a.DateCol <= DATEADD(YEAR,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Last year - Last 365 Days Col2]
FROM
tb1 a
INNER JOIN
tb2 b on a.id=b.fid and a.col3 = b.col4
INNER JOIN
tb3 c on b.fid = c.col5
INNER JOIN
tb4 d on c.id = d.col6
INNER JOIN
tb5 e on c.col7 = e.id
GROUP BY
b.id, d.Title, e.Class
¿Alguien tiene alguna idea de cómo puedo mejorar mi consulta para que se ejecute más rápido?
EDITAR: Me animaron a mover la DATEADD
llamada de función a la where
instrucción y cargar los dos primeros años primero y luego filtrarlos en columnas, pero no estoy seguro de que la respuesta sugerida se ejecute y funcione, se puede encontrar aquí: https: // stackoverflow. com / a / 59944426/12536284
Si está de acuerdo con la solución anterior, muéstreme cómo puedo aplicarla en mi consulta actual.
Solo para su información, estoy usando este SP en C #, Entity Framework (DB-First), algo como esto:
var result = MyDBEntities.CalculatorSP();
Execution Plan
. Por favorRespuestas:
Como ya se mencionó, el plan de ejecución será realmente útil en este caso. Según lo que ha mostrado, parece que ha extraído 12 columnas de 15 columnas en total
tb1 (a)
, por lo que puede intentar ejecutar su consulta sin unir y solotb1
para ver si su consulta funciona como se esperaba. Como no puedo ver nada malo con sus llamadas a la función SUMA, mi mejor suposición es que tiene un problema con sus uniones, sugeriría hacer lo siguiente. Puede comenzar excluyendo la última unión, por ejemplo,INNER JOIN tb5 e on c.col7 = e.id
y cualquier uso relacionado de la misma, comoe.Class as [Class]
ye.Class
en su grupo por declaración. No lo vamos a excluir por completo, esto es solo una prueba para asegurarnos de si el problema está en eso o no, si su consulta se ejecuta mejor y, como se esperaba, puede intentar usar una tabla temporal como solución alternativa en lugar de la última unión , algo como esto:En realidad, las tablas temporales son tablas que existen temporalmente en el servidor SQL. Las tablas temporales son útiles para almacenar los conjuntos de resultados inmediatos a los que se accede varias veces. Puede leer más sobre esto aquí https://www.sqlservertutorial.net/sql-server-basics/sql-server-temporary-tables/ Y aquí https://codingsight.com/introduction-to-temporary-tables-in -servidor SQL/
También recomendaría encarecidamente que, si está utilizando el Procedimiento almacenado, establezca el valor
NOCOUNT
enON
, también puede proporcionar un aumento significativo del rendimiento, ya que el tráfico de red se reduce considerablemente:Basado en esto :
fuente
tb5
a la#Temp
tabla y unirse a la tabla temporal funciona más rápido que unirsetb5
directamente? simplemente contienen los mismos datos (y es#Temp
posible que les falte un índice si existieratb5
). Realmente no puedo entender por qué esto es más eficiente (por lo que sé, debería ser menos eficiente copiar todos los datos y unirse).tb5
pasa si se encuentra en otro servidor? En este caso, usar una tabla temporal es definitivamente más rápido que la unión directa a otro servidor. Eso fue solo una sugerencia para probar y ver si algo ha cambiado. Tuve una situación similar en el pasado, y parece que afortunadamente la tabla temporal también ha ayudado al OP en este caso.El mejor enfoque es insertar en una tabla variable / tabla hash (si el recuento de filas es pequeño, use una variable de tabla o use una tabla hash si el recuento de filas es bastante grande). Luego actualice la agregación y finalmente seleccione de la variable de tabla o tabla hash. Examinar el plan de consulta es necesario.
fuente
Supongo que tb1 es una tabla grande (en relación con tb2, tb3, tb4 y tb5).
Si es así, tiene sentido restringir la selección de esa tabla (con una cláusula WHERE).
Si solo se usa una pequeña parte de tb1, por ejemplo, porque las uniones con tb2, tb3, tb4 y tb5 reducen las filas necesarias a solo un pequeño porcentaje, entonces debe verificar si las tablas están indexadas en las columnas que usa en las uniones .
Si se usa una gran parte de tb1, entonces puede tener sentido agrupar sus resultados antes de unirlo a tb2, tb3, tb4 y tb5. A continuación se muestra un ejemplo de eso.
fuente
Solo use columnas calculadas
Ejemplo
Especificar columnas calculadas en una tabla
fuente
Para optimizar dichos cálculos, puede considerar calcular previamente algunos de los valores. La idea de los cálculos previos es reducir el número de filas que deben leerse o continuar.
Una forma de lograr esto es usar una vista indizada y dejar que el motor haga los cálculos por sí mismo. Como este tipo de vistas tiene algunas limitaciones, puede terminar creando una tabla simple y realizando los cálculos. Básicamente, depende de las necesidades del negocio.
Entonces, en el ejemplo a continuación, estoy creando una tabla con
RowID
yRowDatetime
columnas e insertando 1 millón de filas. Estoy usando una vista indexada para contar las entidades por días, por lo que en lugar de consultar 1 millón de filas por año, consultaré 365 filas por año para contar estas métricas.El éxito de dicha solución depende en gran medida de cómo se distribuyen los datos y cuántas filas tiene. Por ejemplo, si tiene una entrada por día para cada día del año, la vista y la tabla tendrán la misma coincidencia de filas, por lo que las operaciones de E / S no se reducirán.
Además, lo anterior es solo un ejemplo de materializar los datos y leerlos. En su caso, es posible que deba agregar más columnas a la definición de la vista.
fuente
Usaría una tabla de búsqueda "Fechas" para unir mis datos con un índice en DatesId. Utilizo las fechas como filtro cuando quiero buscar datos históricos. La unión es rápida y, por lo tanto, el filtrado como DatesId es el índice primario agrupado (clave primaria). Agregue la columna de fecha (como columna incluida) para su tabla de datos también.
La tabla de fechas tiene las siguientes columnas:
DatesId, Date, Year, Quarter, YearQuarter, MonthNum, MonthNameShort, YearWeek, WeekNum, DayOfYear, DayOfMonth, DayNumOfWeek, DayName
Datos de ejemplo: 20310409 2031-04-09 2031 2 2031-Q2 4 abril abr 2031_15 15 99 9 3 miércoles
Puede enviarme un PM si desea un csv de esto para poder importarlo a la base de datos, pero estoy seguro de que puede encontrar fácilmente algo como esto en línea y hacer el suyo.
También agrego una columna de identidad para que pueda obtener un número entero para cada fecha. Esto hace que sea un poco más fácil trabajar con él, pero no es un requisito.
Esto me permite volver fácilmente a un cierto período. Es bastante fácil crear sus propios puntos de vista sobre esto. Por supuesto, también puede usar la función ROW_NUMBER () para hacer esto durante años, semanas, etc.
Una vez que tengo el rango de fechas que quiero, me uno a los datos. ¡Funciona muy rápido!
fuente
Como siempre está agrupando valores basados en un número entero de meses, primero los agruparía por mes en una subconsulta en la cláusula from. Esto es similar a usar una tabla temporal. No estoy seguro de si esto realmente aceleraría su consulta.
fuente
Para mejorar la velocidad de la consulta SQL, debe agregar índices. Para cada tabla unida, debe agregar un índice.
Como este ejemplo de código para Oracle:
fuente