Estou em uma situação em que quero obter o valor mínimo de 6 colunas.
Encontrei três maneiras até agora de fazer isso, mas tenho preocupações com o desempenho desses métodos e gostaria de saber qual seria melhor para o desempenho.
O primeiro método é usar uma instrução big case . Aqui está um exemplo com 3 colunas, baseado no exemplo do link acima. Minha declaração de caso seria muito mais longa, pois estarei olhando para 6 colunas.
Select Id,
Case When Col1 <= Col2 And Col1 <= Col3 Then Col1
When Col2 <= Col3 Then Col2
Else Col3
End As TheMin
From MyTable
A segunda opção é usar o UNION
operador com várias instruções de seleção . Eu colocaria isso em uma UDF que aceita um parâmetro Id.
select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable
e
select min(col)
from
(
select col1 [col] from MyTable where Id = @id
union all
select col2 from MyTable where Id = @id
union all
select col3 from MyTable where Id = @id
) as t
E a 3ª opção que encontrei foi usar o operador UNPIVOT , que eu nem sabia que existia até agora
with cte (ID, Col1, Col2, Col3)
as
(
select ID, Col1, Col2, Col3
from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
select
ID, min(Amount) as TheMin
from
cte
UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
group by ID
) as minValues
on cte.ID = minValues.ID
Devido ao tamanho da tabela e à frequência em que essa tabela é consultada e atualizada, estou preocupado com o impacto no desempenho que essas consultas teriam no banco de dados.
Esta consulta será realmente usada em uma junção a uma tabela com alguns milhões de registros, porém os registros retornados serão reduzidos para cerca de cem registros por vez. Ele será executado muitas vezes ao longo do dia, e as 6 colunas que estou consultando são atualizadas com frequência (elas contêm estatísticas diárias). Acho que não há índices nas 6 colunas que estou consultando.
Qual desses métodos é melhor para o desempenho ao tentar obter o mínimo de várias colunas? Ou existe outro método melhor que eu não conheço?
Estou usando o SQL Server 2005
Dados e resultados de amostra
Se meus dados contivessem registros como este:
Id Col1 Col2 Col3 Col4 Col5 Col6 1 3 4 0 2 1 5 2 2 6 10 5 7 9 3 1 1 2 3 4 5 4 9 5 4 6 8 9
O resultado final deve ser
Valor do código 1 0 2 2 3 1 4 4
Testei o desempenho de todos os 3 métodos e aqui está o que encontrei:
UNION
a subconsulta foi um pouco mais lenta. ACASE WHEN
consulta é um pouco mais rápida que aUNPIVOT
única.UNION
a subconsulta é significativamente mais lenta, masUNPIVOT
a consulta se torna um pouco mais rápida que aCASE WHEN
consultaUNION
a subconsulta ainda é significativamente mais lenta, masUNPIVOT
se torna muito mais rápida que aCASE WHEN
consultaAssim, os resultados finais parecem ser
Com conjuntos de discos menores, não parece haver diferença suficiente para importar. Use o que for mais fácil de ler e manter.
Quando você começa a entrar em conjuntos de registros maiores, a
UNION ALL
subconsulta começa a ter um desempenho ruim em comparação com os outros dois métodos.A
CASE
instrução tem o melhor desempenho até um certo ponto (no meu caso, cerca de 100 mil linhas), e em qual ponto aUNPIVOT
consulta se torna a consulta de melhor desempenhoO número real em que uma consulta se torna melhor que outra provavelmente mudará como resultado de seu hardware, esquema de banco de dados, dados e carga atual do servidor, portanto, certifique-se de testar com seu próprio sistema se estiver preocupado com o desempenho.
Também fiz alguns testes usando a resposta do Mikael ; no entanto, foi mais lento do que todos os 3 outros métodos tentados aqui para a maioria dos tamanhos de conjuntos de registros. A única exceção foi que foi melhor do que a
UNION ALL
consulta para tamanhos de conjuntos de registros muito grandes. Eu gosto do fato de mostrar o nome da coluna além do menor valor.Não sou dba, então posso não ter otimizado meus testes e ter perdido alguma coisa. Eu estava testando com os dados reais ao vivo, então isso pode ter afetado os resultados. Tentei explicar isso executando cada consulta algumas vezes diferentes, mas nunca se sabe. Eu definitivamente estaria interessado se alguém escrevesse um teste limpo disso e compartilhasse seus resultados.
Não sei o que é mais rápido, mas você pode tentar algo assim.
Resultado:
Se você não estiver interessado em qual coluna tem o valor mínimo, você pode usar isso.
Uma consulta não dinâmica simplificada.
Adicione uma coluna computada persistente que usa uma
CASE
instrução para fazer a lógica necessária.O valor mínimo estará sempre disponível de forma eficiente quando você precisar fazer uma junção (ou qualquer outra coisa) com base nesse valor.
O valor será recalculado toda vez que qualquer um dos valores de origem for alterado (
INSERT
/UPDATE
/MERGE
). Não estou dizendo que essa é necessariamente a melhor solução para a carga de trabalho, apenas a ofereço como uma solução, assim como as outras respostas. Somente o OP pode determinar qual é o melhor para a carga de trabalho.Sua
case
declaração não é eficiente. Você está fazendo 5 comparações no pior caso e 2 no melhor caso; ao passo que encontrar o mínimo den
deve fazer no máximon-1
comparações.Para cada linha, em média, você está fazendo 3,5 comparações em vez de 2. Assim, leva mais tempo de CPU e é lento. Tente seus testes novamente usando a
case
instrução abaixo. Ele está usando apenas 2 comparações por linha e deve ser mais eficiente queunpivot
eunion all
.O
union all
método está errado no seu caso, pois você está obtendo o valor mínimo não por linha, mas por toda a tabela. Além disso, não será eficiente, pois você fará a varredura da mesma tabela 3 vezes. Quando a tabela é pequena, a E/S não fará muita diferença, mas para tabelas grandes fará. Não use esse método.Unpivot
é bom e tente desarticular manualmente também usando a junção cruzada da sua tabela com(select 1 union all select 2 union all select 3)
. Deve ser tão eficiente quanto ounpivot
.A melhor solução seria ter uma coluna persistente computada, se você não tiver problemas de espaço. Ele aumentará o tamanho da linha em 4 bytes (suponho que você terá
int
tipo), o que, por sua vez, aumentará o tamanho da tabela.No entanto, o espaço e a memória são problemas em seu sistema e a CPU não é, então, não persista, mas use uma coluna computada simples usando a instrução case. Isso tornará o código mais simples.
Declaração de caso para 6 datas. Para fazer menos, copie a ramificação verdadeira da primeira instrução case. O pior caso é quando Date1 é o valor mais baixo, o melhor caso é quando Date6 é o valor mais baixo, então coloque a data mais provável em Date6. Eu escrevi isso por causa das limitações das colunas computadas.
Se você se deparou com esta página simplesmente procurando comparar datas e não está tão preocupado com desempenho ou compatibilidade, você pode usar um construtor de valor de tabela, que pode ser usado sempre que subseleções são permitidas (SQL Server 2008 e superior):
Eu acho que a primeira opção é a mais rápida (embora não pareça muito boa do ponto de vista da programação!). Isso ocorre porque ele lida com exatamente N linhas (onde N é o tamanho da tabela) e não precisa pesquisar ou classificar como o método 2 ou 3.
Um teste com amostra grande deve provar o ponto.
Outra opção a ser considerada (como se você precisasse de mais!), é criar uma visão materializada sobre sua mesa. se o tamanho da sua tabela estiver em centenas de milhares ou mais. Dessa forma, o valor mínimo é calculado enquanto a linha é alterada e a tabela inteira não precisaria ser processada com cada consulta. No SQL Server, as visualizações materializadas são chamadas de visualizações indexadas