Eu tenho uma visão ampla que uso de dentro de um aplicativo. Acho que reduzi meu problema de desempenho, mas não tenho certeza de como corrigi-lo. Uma versão simplificada da exibição é assim:
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
Isso provavelmente não justifica toda a razão da estrutura da consulta, mas talvez lhe dê uma idéia - essa visão une duas tabelas mal projetadas que eu não tenho controle e tenta sintetizar algumas informações dela.
Portanto, como essa é uma visão usada do aplicativo, ao tentar otimizar, envolvo-a em outro SELECT, assim:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
porque o aplicativo está procurando membros específicos da equipe no resultado.
O problema parece ser a COALESCE(pe.StaffName, se.StaffName) AS StaffNameseção e estou selecionando a partir da exibição StaffName. Se eu mudar para pe.StaffName AS StaffNameou se.StaffName AS StaffName, os problemas de desempenho desaparecerão (mas consulte a atualização 2 abaixo) . Mas isso não acontece porque um lado ou o outro FULL OUTER JOINpode estar ausente, portanto, um ou outro campo pode ser NULL.
Posso refatorar isso substituindo o COALESCE(…)por outra coisa, que será reescrita na subconsulta?
Outras notas:
- Eu já adicionei alguns índices para corrigir problemas de desempenho com o restante da consulta - sem
COALESCEque seja muito rápido. - Para minha surpresa, observar o plano de execução não gera nenhum sinalizador, mesmo quando a subconsulta e a
WHEREinstrução de empacotamento estão incluídas. Meu custo total de subconsulta no analisador é0.0065736. Hmph. Demora quatro segundos para executar. - Alterar o aplicativo para consultar de maneira diferente
(por exemplo, retornarpode funcionar, mas como último recurso - espero realmente otimizar a exibição sem precisar recorrer ao toque no aplicativo.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffNamee executarWHERE PEStaffName = 'X' OR SEStaffName = 'X') - Um procedimento armazenado provavelmente faria mais sentido para isso, mas o aplicativo é construído com o Entity Framework, e eu não conseguia descobrir como fazê-lo funcionar bem com um SP que retorna um tipo de tabela (outro tópico inteiramente).
Índices
Os índices que adicionei até agora são mais ou menos assim:
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])
Atualizar
Hmm ... eu tentei simular a mudança atingida acima, e não ajudou. Ou seja, antes ) Z, acrescentei AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q'), mas o desempenho é o mesmo. Agora realmente não sei por onde começar.
Atualização 2
O comentário do @ypercube sobre a necessidade da junção completa me fez perceber que minha consulta sintetizada deixou de fora um componente provavelmente importante. Embora, sim, eu precise da junção completa, o teste que fiz acima, eliminando COALESCEe testando apenas um lado da junção para um valor não nulo, tornaria o outro lado da junção completa irrelevante , e o otimizador provavelmente estava usando este fato para acelerar a consulta. Além disso, atualizei o exemplo para mostrar que, StaffNamena verdade, é uma das chaves de junção - que provavelmente tem uma influência significativa sobre a questão. Agora também estou inclinado a sugerir que quebrar isso em uma união de três vias, em vez de uma união completa, pode ser a resposta e simplificará a abundância de COALESCEs que estou fazendo de qualquer maneira. Tentando agora.
KeyField, ambos indexam INCLUDEo StaffNamecampo e vários outros campos. Eu posso postar as definições de índice na pergunta. Estou trabalhando nisso em um servidor de teste para adicionar quaisquer índices que você julgue úteis!
WHERE pe.ThisThing = 1 AND se.OtherThing = 0condição que cancela a FULL OUTERjunção e torna a consulta equivalente a uma junção interna. Tem certeza de que precisa de uma associação COMPLETA?
INNER JOIN, LEFT JOINcom WHERE IS NULLcheque, RIGHT JOIN com IS NULL) e, em seguida, UNION ALLas três partes. Dessa forma, não haverá necessidade de uso COALESCE()e poderá (apenas poderá) ajudar o otimizador a descobrir a reescrita.