Qual é a maneira mais eficiente de obter o mínimo de várias colunas no SQL Server 2005?

29

Estou em uma situação em que quero obter o valor mínimo de 6 colunas.

Até agora, encontrei três maneiras de conseguir isso, mas tenho preocupações com o desempenho desses métodos e gostaria de saber qual seria o melhor para o desempenho.

O primeiro método é usar uma declaração de maiúsculas e minúsculas . Aqui está um exemplo com 3 colunas, com base no exemplo no link acima. Minha declaração de caso seria muito mais longa, já que analisarei 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 UNIONoperador com várias instruções de seleção . Eu colocaria isso em um 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 terceira 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 com que essa tabela é consultada e atualizada, estou preocupado com o impacto no desempenho dessas consultas no banco de dados.

Essa consulta será realmente usada em uma associação a uma tabela com alguns milhões de registros, no entanto, os registros retornados serão reduzidos para cerca de cem registros por vez. Ele será executado várias vezes ao longo do dia, e as 6 colunas que estou consultando são atualizadas frequentemente (elas contêm estatísticas diárias). Eu não acho que existem í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?

Eu estou usando o SQL Server 2005

Dados e resultados de amostra

Se meus dados contiverem 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 ID
1 0
2 2
3 1
4 4
Rachel
fonte

Respostas:

22

Testei o desempenho de todos os três métodos, e aqui está o que encontrei:

  • 1 registro: nenhuma diferença perceptível
  • 10 registros: nenhuma diferença perceptível
  • 1.000 registros: nenhuma diferença perceptível
  • 10.000 registros: a UNIONsubconsulta foi um pouco mais lenta. A CASE WHENconsulta é um pouco mais rápida que aUNPIVOT .
  • 100.000 registros: a UNIONsubconsulta é significativamente mais lenta, mas a UNPIVOTconsulta se torna um pouco mais rápida que aCASE WHEN consulta
  • 500.000 registros: a UNIONsubconsulta ainda é significativamente mais lenta, mas UNPIVOTse torna muito mais rápida que a CASE WHENconsulta

Portanto, os resultados finais parecem ser

  • Com conjuntos de registros menores, não parece haver diferença suficiente para importar. Use o que for mais fácil de ler e manter.

  • Depois que você começa a entrar em conjuntos de registros maiores, a UNION ALLsubconsulta começa a apresentar um desempenho ruim em comparação com os outros dois métodos.

  • A CASEinstrução tem o melhor desempenho até um certo ponto (no meu caso, cerca de 100 mil linhas) e em que ponto a UNPIVOTconsulta se torna a consulta com melhor desempenho

O número real no qual uma consulta se torna melhor que a outra provavelmente mudará como resultado do seu hardware, esquema do banco de dados, dados e carga atual do servidor, portanto, teste com seu próprio sistema se você estiver preocupado com o desempenho.

Também fiz alguns testes usando a resposta de Mikael ; no entanto, foi mais lento que todos os outros três métodos tentados aqui para a maioria dos tamanhos de conjunto de registros. A única exceção foi que foi melhor do que uma UNION ALLconsulta para tamanhos muito grandes de conjuntos de registros. Eu gosto do fato de mostrar o nome da coluna, além do menor valor.

Como não sou dba, talvez não tenha otimizado meus testes e tenha perdido algo. Eu estava testando com os dados reais, para que isso possa ter afetado os resultados. Tentei explicar isso executando cada consulta algumas vezes diferentes, mas você nunca sabe. Eu ficaria definitivamente interessado se alguém escrevesse um teste limpo disso e compartilhasse seus resultados.

Rachel
fonte
6

Não saiba o que é mais rápido, mas você pode tentar algo assim.

declare @T table
(
  Col1 int,
  Col2 int,
  Col3 int,
  Col4 int,
  Col5 int,
  Col6 int
)

insert into @T values(1, 2, 3, 4, 5, 6)
insert into @T values(2, 3, 1, 4, 5, 6)

select T4.ColName, T4.ColValue
from @T as T1
  cross apply (
                select T3.ColValue, T3.ColName
                from (
                       select row_number() over(order by T2.ColValue) as rn,
                              T2.ColValue,
                              T2.ColName
                       from (
                              select T1.Col1, 'Col1' union all
                              select T1.Col2, 'Col2' union all
                              select T1.Col3, 'Col3' union all
                              select T1.Col4, 'Col4' union all
                              select T1.Col5, 'Col5' union all
                              select T1.Col6, 'Col6'
                            ) as T2(ColValue, ColName)
                     ) as T3
                where T3.rn = 1
              ) as T4

Resultado:

ColName ColValue
------- -----------
Col1    1
Col3    1

Se você não estiver interessado em qual coluna tem o valor mínimo, use-o.

declare @T table
(
  Id int,
  Col1 int,
  Col2 int,
  Col3 int,
  Col4 int,
  Col5 int,
  Col6 int
)

insert into @T
select 1,        3,       4,       0,       2,       1,       5 union all
select 2,        2,       6,      10,       5,       7,       9 union all
select 3,        1,       1,       2,       3,       4,       5 union all
select 4,        9,       5,       4,       6,       8,       9

select T.Id, (select min(T1.ColValue)
              from (
                      select T.Col1 union all
                      select T.Col2 union all
                      select T.Col3 union all
                      select T.Col4 union all
                      select T.Col5 union all
                      select T.Col6
                    ) as T1(ColValue)
             ) as ColValue
from @T as T

Uma consulta não dinâmica simplificada.

select Id, min(ColValue) as ColValue
from @T
unpivot (ColValue for Col in (Col1, Col2, Col3, Col4, Col5, Col6)) as U
group by Id
Mikael Eriksson
fonte
6

Adicione uma coluna computada persistente que use uma CASEinstrução para fazer a lógica necessária.

O valor mínimo estará sempre disponível com eficiência quando você precisar fazer uma associação (ou qualquer outra coisa) com base nesse valor.

O valor será recalculado sempre que qualquer um dos valores de origem for alterado ( INSERT/ UPDATE/ MERGE). Eu não estou dizendo que isso é necessariamente a melhor solução para a carga de trabalho, eu apenas oferecê-lo como uma solução, assim como as outras respostas. Somente o OP pode determinar qual é o melhor para a carga de trabalho.

Jon Seigel
fonte
1

Declaração do caso por 6 datas. Para fazer menos, copie o ramo verdadeiro da primeira instrução de caso. O pior caso é quando Data1 é o valor mais baixo, o melhor caso é quando Data6 é o valor mais baixo, portanto, coloque a data mais provável em Data6. Eu escrevi isso por causa das limitações das colunas computadas.

CASE WHEN Date1 IS NULL OR Date1 > Date2 THEN
        CASE WHEN Date2 IS NULL OR Date2 > Date3 THEN
            CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
                CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                        Date6
                    ELSE
                        Date4
                    END
                END
            ELSE
                CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
                        Date6
                    ELSE
                        Date3
                    END
                END
            END
        ELSE
            CASE WHEN Date2 IS NULL OR Date2 > Date4 THEN
                CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                        CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                            Date6
                        ELSE
                            Date5
                        END
                    ELSE
                        CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                            Date6
                        ELSE
                            Date4
                        END
                    END
                END
            ELSE
                CASE WHEN Date2 IS NULL OR Date2 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date2 IS NULL OR Date2 > Date6 THEN
                        Date6
                    ELSE
                        Date2
                    END
                END
            END
        END
ELSE
    CASE WHEN Date1 IS NULL OR Date1 > Date3 THEN
        CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
            CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                    Date6
                ELSE
                    Date4
                END
            END
        ELSE
            CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
                    Date6
                ELSE
                    Date3
                END
            END
        END
    ELSE
        CASE WHEN Date1 IS NULL OR Date1 > Date4 THEN
            CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                    Date6
                ELSE
                    Date4
                END
            END
        ELSE
            CASE WHEN Date1 IS NULL OR Date1 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date1 IS NULL OR Date1 > Date6 THEN
                    Date6
                ELSE
                    Date1
                END
            END
        END
    END
END

Se você se deparou com esta página simplesmente procurando comparar datas e não está tão preocupado com desempenho ou compatibilidade, pode usar um Construtor de Valor de Tabela, que pode ser usado sempre que as sub-seleções forem permitidas (SQL Server 2008 e superior):

Lowest =    
(
    SELECT MIN(TVC.d) 
    FROM 
    (
        VALUES
            (Date1), 
            (Date2), 
            (Date3), 
            (Date4), 
            (Date5), 
            (Date6)
    ) 
    AS TVC(d)
)
Jesse Adam
fonte
1

Sua casedeclaração não é eficiente. Você está fazendo 5 comparações no pior dos casos e 2 no melhor dos casos; Considerando que encontrar o mínimo de ndeve fazer no máximon-1 comparações.

Para cada linha, em média, você está fazendo 3,5 comparações em vez de 2. Portanto, está demorando mais tempo na CPU e é lento. Tente seus testes novamente usando a caseinstrução abaixo . Ele está usando apenas 2 comparações por linha e deve ser mais eficiente que unpivote union all.

Select Id, 
       Case 
           When Col1 <= Col2 then case when Col1 <= Col3 Then Col1  else col3 end
            When  Col2 <= Col3 Then Col2  
            Else Col3 
            End As TheMin 
From   YourTableNameHere

o union all método está errado no seu caso, pois você está obtendo o valor mínimo não por linha, mas para toda a tabela. Além disso, não será eficiente, pois você digitalizará a mesma tabela três vezes. Quando a tabela é pequena, a E / S não fará muita diferença, mas para tabelas grandes, ela fará. Não use esse método.

Unpivoté bom e tente fazer o giro manual também, usando a junção cruzada à sua mesa (select 1 union all select 2 union all select 3). Deve ser tão eficiente quanto o unpivot.

A melhor solução seria ter uma coluna persistente computada, se você não tiver problemas de espaço. Isso aumentará o tamanho da linha em 4 bytes (suponho que você tenha um inttipo), o que, por sua vez, aumentará o tamanho da tabela.

No entanto, espaço e memória são problemas no seu sistema e a CPU não é, então, não a mantém persistente, mas usa uma coluna computada simples usando a instrução case. Isso tornará o código mais simples.

Gulli Meel
fonte
-1

Eu acho que a primeira opção é a mais rápida (embora não pareça muito lisa da perspectiva da programação!). Isso ocorre porque ele lida exatamente com N linhas (onde N é o tamanho da tabela) e não precisa pesquisar nem classificar como o método 2 ou 3.

Um teste com amostra grande deve provar o ponto.

Como outra opção a considerar (como se você precisar de mais!), É criar uma visão materializada sobre sua tabela. se o tamanho da sua mesa estiver em centenas de milhares ou mais. Dessa forma, o valor mínimo é calculado enquanto a linha é alterada e a tabela inteira não precisa ser processada com todas as consultas. No SQL Server, as visualizações materializadas são chamadas de Exibições Indexadas

NoChance
fonte
-1
Create table #temp
   (
    id int identity(1,1),
    Name varchar(30),
    Year1 int,
    Year2 int,
    Year3 int,
    Year4 int
   )

   Insert into #temp values ('A' ,2015,2016,2014,2010)
   Insert into #temp values ('B' ,2016,2013,2017,2018)
   Insert into #temp values ('C' ,2010,2016,2014,2017)
   Insert into #temp values ('D' ,2017,2016,2014,2015)
   Insert into #temp values ('E' ,2016,2016,2016,2016)
   Insert into #temp values ('F' ,2016,2017,2018,2019)
   Insert into #temp values ('G' ,2016,2017,2020,2019)

   Select *, Case 
                 when Year1 >= Year2 and Year1 >= Year3 and Year1 >= Year4 then Year1
                 when Year2 >= Year3 and Year2 >= Year4 and Year2 >= Year1 then Year2
                 when Year3 >= Year4 and Year3 >= Year1 and Year3 >= Year2 then Year3
                 when Year4 >= Year1 and Year4 >= Year2 and Year4 >= Year3 then Year4  
                 else Year1 end as maxscore  
                 from #temp
Ravi
fonte
Você não está contabilizando NULLs - isso torna sua expressão CASE relativamente simples. No entanto, se pelo menos uma das colunas for NULL, sua solução retornará Year1como resultado, o que pode não estar necessariamente correto.
Andriy H