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 StaffName
seção e estou selecionando a partir da exibição StaffName
. Se eu mudar para pe.StaffName AS StaffName
ou 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 JOIN
pode 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
COALESCE
que seja muito rápido. - Para minha surpresa, observar o plano de execução não gera nenhum sinalizador, mesmo quando a subconsulta e a
WHERE
instruçã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 SEStaffName
e 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 COALESCE
e 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, StaffName
na 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 COALESCE
s que estou fazendo de qualquer maneira. Tentando agora.
KeyField
, ambos indexam INCLUDE
o StaffName
campo 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 = 0
condição que cancela a FULL OUTER
junção e torna a consulta equivalente a uma junção interna. Tem certeza de que precisa de uma associação COMPLETA?
INNER JOIN
, LEFT JOIN
com WHERE IS NULL
cheque, RIGHT JOIN com IS NULL) e, em seguida, UNION ALL
as três partes. Dessa forma, não haverá necessidade de uso COALESCE()
e poderá (apenas poderá) ajudar o otimizador a descobrir a reescrita.