Tengo una vista amplia que uso desde una aplicación. Creo que he reducido mi problema de rendimiento, pero no estoy seguro de cómo solucionarlo. Una versión simplificada de la vista se ve así:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Probablemente eso no justifique la razón completa de la estructura de la consulta, pero tal vez le dé una idea: esta vista se une a dos tablas muy mal diseñadas sobre las que no tengo control e intenta sintetizar cierta información.
Entonces, dado que esta es una vista utilizada desde la aplicación, mientras trato de optimizar, la envuelvo en otro SELECT, así:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
porque la aplicación está buscando miembros del personal específicos en el resultado.
El problema parece ser la COALESCE(pe.StaffName, se.StaffName) AS StaffName
sección, y que estoy seleccionando de la vista en adelante StaffName
. Si cambio eso pe.StaffName AS StaffName
ao se.StaffName AS StaffName
, los problemas de rendimiento desaparecen (pero vea la actualización 2 a continuación) . Pero eso no funcionará porque FULL OUTER JOIN
podría faltar un lado u otro , por lo que uno u otro campo puede ser NULL.
¿Puedo refactorizar esto reemplazando el COALESCE(…)
con algo más, que se reescribirá en la subconsulta?
Otras notas:
- Ya he agregado algunos índices para solucionar problemas de rendimiento con el resto de la consulta, sin la
COALESCE
cual es muy rápido. - Para mi sorpresa, mirar el plan de ejecución no levanta ninguna bandera, incluso cuando
WHERE
se incluye la subconsulta de ajuste y la declaración. Mi costo total de subconsulta en el analizador es0.0065736
. Hmph Tarda cuatro segundos en ejecutarse. - Cambiar la aplicación para consultar de manera diferente
(por ejemplo, regresarpodría funcionar, pero como último recurso, realmente espero poder optimizar la vista sin tener que recurrir a tocar la aplicación.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
y hacerWHERE PEStaffName = 'X' OR SEStaffName = 'X'
) - Un procedimiento almacenado probablemente tendría más sentido para esto, pero la aplicación está construida con Entity Framework, y no pude descubrir cómo hacer que funcione bien con un SP que devuelve un tipo de tabla (otro tema por completo).
Índices
Los índices que he agregado hasta ahora se parecen a esto:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Actualizar
Hmm ... Intenté simular el cambio afectado arriba, y no sirvió de nada. Es decir, antes de lo ) Z
anterior, agregué AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
, pero el rendimiento es el mismo. Ahora realmente no sé por dónde empezar.
Actualización 2
El comentario de @ypercube sobre la necesidad de la unión completa me hizo darme cuenta de que mi consulta sintetizada dejaba fuera un componente probablemente importante. Si bien, sí, necesito la combinación completa, la prueba que hice anteriormente al soltar COALESCE
y probar solo un lado de la combinación para obtener un valor no nulo haría que el otro lado de la combinación completa sea irrelevante , y el optimizador probablemente estaba usando esto hecho para acelerar la consulta. Además, he actualizado el ejemplo para mostrar que en StaffName
realidad es una de las claves de combinación, lo que probablemente tenga una relación significativa con la pregunta. Ahora también me inclino hacia su sugerencia de que dividir esto en una unión de tres vías en lugar de una unión completa puede ser la respuesta, y simplificará la abundancia de COALESCE
s que estoy haciendo de todos modos. Probándolo ahora.
KeyField
, ambos indexan INCLUDE
el StaffName
campo y varios otros campos. Puedo publicar las definiciones de índice en la pregunta. Estoy trabajando en esto en un servidor de prueba para poder agregar cualquier índice que creas que podría ser útil.
WHERE pe.ThisThing = 1 AND se.OtherThing = 0
condición que cancela la FULL OUTER
unión y hace que la consulta sea equivalente a una unión interna. ¿Estás seguro de que necesitas una unión COMPLETA?
INNER JOIN
, LEFT JOIN
con WHERE IS NULL
cheque, RIGHT JOIN con IS NULL) y luego UNION ALL
las tres partes. De esta forma no habrá necesidad de usar COALESCE()
y podría (solo podría) ayudar al optimizador a descubrir la reescritura.