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.
fonte
KeyField
, ambos indexamINCLUDE
oStaffName
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 aFULL 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
comWHERE IS NULL
cheque, RIGHT JOIN com IS NULL) e, em seguida,UNION ALL
as três partes. Dessa forma, não haverá necessidade de usoCOALESCE()
e poderá (apenas poderá) ajudar o otimizador a descobrir a reescrita.Respostas:
Isso foi muito demorado, mas como o OP diz que funcionou, estou adicionando-o como resposta (fique à vontade para corrigi-lo se encontrar algo errado).
Tente dividir a consulta interna em três partes (
INNER JOIN
,LEFT JOIN
comWHERE IS NULL
cheque,RIGHT JOIN
comIS NULL
cheque) e depoisUNION ALL
nas três partes. Isso tem as seguintes vantagens:O otimizador possui menos opções de transformação disponíveis para
FULL
junções do que para (as mais comuns)INNER
eLEFT
junções.A
Z
tabela derivada pode ser removida (você pode fazer isso de qualquer maneira) da definição da visualização.O
NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
serão necessários apenas naINNER
parte aderir.Pequenas melhorias, o uso
COALESCE()
será mínimo, se houver (presumi quese.SEId
epe.PEId
não sejam anuláveis. Se mais colunas não forem anuláveis, você poderá remover maisCOALESCE()
chamadas.)Mais importante, o otimizador pode empurrar para baixo quaisquer condições em suas consultas que envolvem essas colunas (agora que
COALESCE()
não estão bloqueando o envio).Todas as opções acima fornecerão ao otimizador mais opções para transformar / reescrever qualquer consulta que use a visualização, para que ele encontre um plano de execução que possa ser usado nos índices das tabelas subjacentes.
Ao todo, a visualização pode ser escrita como:
fonte
Minha intuição seria que isso não deveria ser um problema, já que, no momento em
COALESCE(pe.StaffName, se.StaffName) AS StaffName
que algo faz com que todas as linhas das duas fontes já tenham sido extraídas e correspondidas, a chamada de função é uma simples comparação na memória com a nula e -escolher. Obviamente, esse não é o caso, então talvez algo em uma das fontes (se forem visualizações ou tabelas derivadas em linha) ou nas tabelas base (por exemplo, falta de índices) está fazendo o planejador de consultas achar que precisa examinar essas colunas separadamente.Sem mais detalhes da consulta exata que você está executando, das estruturas de suporte e dos planos de consulta produzidos, tudo o que sugerimos é conjectura.
Para tentar forçar a comparação a ser feita depois de tudo, tente selecionar os dois valores na tabela abaixo (
pe.StaffName AS pe.StaffName, se.StaffName AS seStaffName
) e faça a seleção na consulta externa (COALESCE(peStaffName, seStaffName) AS StaffName
) ou até mesmo enviar os dados da consulta interna para uma tabela temporária faz a consulta externa selecionando-a (mas isso exigiria um procedimento armazenado e, dependendo do número de linhas, esse dump para tempdb pode ser caro e, portanto, problemático por si só).fonte
Z
atualmente volta com ~ 1.5m linhas. O que eu quero fazer é reescrever esse predicado na consulta paraZ
que ele use os índices ... mas agora também estou confuso porque quando eu coloco o predicado manualmente lá, ele ainda não usa um índice ... então agora Não tenho certeza.