Obtendo o mínimo de dois valores no SQL

180

Eu tenho duas variáveis, uma é chamada PaidThisMonthe a outra é chamada OwedPast. Ambos são resultados de algumas subconsultas no SQL. Como posso selecionar o menor dos dois e retorná-lo como um valor intitulado PaidForPast?

A MINfunção funciona em colunas, não em variáveis.

Malfist
fonte
1
Se você estiver no Postgres ou no MySQL, pule para a resposta de @ Gil_Margolin.
Noumenon

Respostas:

127

Caso de uso:

   Select Case When @PaidThisMonth < @OwedPast 
               Then @PaidThisMonth Else @OwedPast End PaidForPast

Como tabela Inline com valor UDF

CREATE FUNCTION Minimum
(@Param1 Integer, @Param2 Integer)
Returns Table As
Return(Select Case When @Param1 < @Param2 
                   Then @Param1 Else @Param2 End MinValue)

Uso:

Select MinValue as PaidforPast 
From dbo.Minimum(@PaidThisMonth, @OwedPast)

ADENDO: Provavelmente é melhor para quando abordar apenas dois valores possíveis, se houver mais de dois, considere a resposta de Craig usando a cláusula Values.

Charles Bretana
fonte
sintaxe mais compreensível: return (selecione minValue = case quando @@ param1 <@@ param2 e @@ param1 else @@ param2 end). Ok, isso pode não ser normalizado, eu não sei. Mas é muito mais compreensível e deve ser normalizado.
Softlion
1
Outro motivo para preferir a resposta do @ Craig abaixo é devido ao tratamento nulo. Se os valores comparados forem anuláveis ​​e um dos valores comparados for nulo, a caixa de opção mostrada pode retornar nulo ou o valor, dependendo da ordem do teste WHEN (a menos que você inclua ISNULL). A abordagem de Craig sempre preferirá a seleção do valor não nulo que me parece mais correto, pelo menos no meu caso de uso atual na comparação de datas nuláveis.
Nij
148

O SQL Server 2012 e 2014 oferece suporte à função IIF (cont, true, false). Assim, para uma seleção mínima, você pode usá-lo como

SELECT IIF(first>second, second, first) the_minimal FROM table

Embora o IIF seja apenas um atalho para escrever CASE...WHEN...ELSE, é mais fácil escrever.

Mert Gülsoy
fonte
8
IIFé apenas um açúcar sintático para CASE...WHEN...ELSE.
Salman Um
55
Possivelmente sim. Mas mais fácil de escrever.
Mert Gülsoy 15/04/2015
1
@ MertGülsoy E mais fácil de ler, que deve estar no topo da lista de prioridades de todos, logo após a correção.
Daniel
118

As soluções que utilizam CASE, IIF e UDF são adequadas, mas impraticáveis ​​ao estender o problema ao caso geral usando mais de 2 valores de comparação. A solução generalizada no SQL Server 2008+ utiliza um aplicativo estranho da cláusula VALUES:

SELECT
PaidForPast=(SELECT MIN(x) FROM (VALUES (PaidThisMonth),(OwedPast)) AS value(x))

Crédito devido a este site: http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-maximum-value-from-some-columns-sql- server-t-sql.aspx

Craig
fonte
12
Esta é a melhor resposta
FindOutIslamNow 23/08
se quiser que a min non-zero:MIN(x*(case x when 0 then null else 1 end))
MPAG
Exceto Martinc deu a mesma resposta, quatro anos antes, e realmente mostrou-lo com mais de dois valores ...
Auspex
4
Auspex, a resposta de MartinC não tem relação. Esta resposta não usa sindicatos.
Craig
30

Eu apenas tive uma situação em que tive que encontrar o máximo de 4 seleções complexas em uma atualização. Com essa abordagem, você pode ter quantas quiser!

Você também pode substituir os números por seleções adicionais

select max(x)
 from (
 select 1 as 'x' union
 select 4 as 'x' union
 select 3 as 'x' union
 select 2 as 'x' 
 ) a

Uso mais complexo

 @answer = select Max(x)
           from (
                select @NumberA as 'x' union
                select @NumberB as 'x' union
                select @NumberC as 'x' union
                select (
                       Select Max(score) from TopScores
                       ) as 'x' 
     ) a

Tenho certeza que um UDF tem melhor desempenho.

MartinC
fonte
Eu gosto mais desse, pois é SQL básico. Além disso, as UDFs não são necessariamente mais rápidas. Para a maioria dos armazenamentos de colunas, cada atributo (presumo que você também filtre os atributos) pode ser calculado em paralelo e apenas o conjunto de qualificações é unido. Portanto, os sindicatos não são lentos em si.
Bouncner
simples e incrível.
ashleedawg
22

Para MySQL ou PostgreSQL 9.3+, uma maneira melhor é usar as funções LEASTe GREATEST.

SELECT GREATEST(A.date0, B.date0) AS date0, 
       LEAST(A.date1, B.date1, B.date2) AS date1
FROM A, B
WHERE B.x = A.x

Com:

  • GREATEST(value [, ...]): Retorna o maior argumento (com valor máximo) dos valores fornecidos
  • LEAST(value [, ...])Retorna o menor argumento (com valor mínimo) dos valores fornecidos

Links para documentação:

Gil Margolin
fonte
Isso também funciona em PostgreSQL (e é exatamente o que eu estava procurando :) Veja: postgresql.org/docs/9.5/static/functions-conditional.html
Albert Vaca Cintora
1
Esta é a melhor resposta de longe.
Roberto Rodriguez
2
@RobertoRodriguez, seria o melhor se a pergunta tivesse o MySQL ou o PostgreSQL marcado como parte da pergunta. A pergunta era especificamente sobre tsql, portanto, essa resposta não ajuda em nada.
21919 Jmaurier
isto não é resposta para MSSQL
Mujah Maskey
13

Aqui está um truque se você deseja calcular o máximo (campo, 0):

SELECT (ABS(field) + field)/2 FROM Table

retorna 0 se fieldfor negativo; caso contrário, retorne field.

mathematix
fonte
3
Portanto, para calcular o mínimo (@a, @b), você pode usar:SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2
scottyc 28/01
1
e não se esqueça sobre o tipo de excesso;)
pkuderov
Isso é salvo do ponto de vista de precisão de ponto flutuante? É certo que o resultado nunca será próximo de zero, mas negativo?
zuraff
6

Use uma instrução CASE.

O exemplo B desta página deve estar próximo do que você está tentando fazer:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Aqui está o código da página:

USE AdventureWorks;
GO
SELECT   ProductNumber, Name, 'Price Range' = 
      CASE 
         WHEN ListPrice =  0 THEN 'Mfg item - not for resale'
         WHEN ListPrice < 50 THEN 'Under $50'
         WHEN ListPrice >= 50 and ListPrice < 250 THEN 'Under $250'
         WHEN ListPrice >= 250 and ListPrice < 1000 THEN 'Under $1000'
         ELSE 'Over $1000'
      END
FROM Production.Product
ORDER BY ProductNumber ;
GO
Mike Cole
fonte
2

Use uma tabela temporária para inserir o intervalo de valores e selecione o mínimo / máximo da tabela temporária em um procedimento armazenado ou UDF. Essa é uma construção básica, portanto, fique à vontade para revisar conforme necessário.

Por exemplo:

CREATE PROCEDURE GetMinSpeed() AS
BEGIN

    CREATE TABLE #speed (Driver NVARCHAR(10), SPEED INT);
    '
    ' Insert any number of data you need to sort and pull from
    '
    INSERT INTO #speed (N'Petty', 165)
    INSERT INTO #speed (N'Earnhardt', 172)
    INSERT INTO #speed (N'Patrick', 174)

    SELECT MIN(SPEED) FROM #speed

    DROP TABLE #speed

END
user1970604
fonte
2

Isso funciona por até 5 datas e lida com nulos. Apenas não conseguia fazê-lo funcionar como uma função embutida.

CREATE FUNCTION dbo.MinDate(@Date1 datetime = Null,
                            @Date2 datetime = Null,
                            @Date3 datetime = Null,
                            @Date4 datetime = Null,
                            @Date5 datetime = Null)
RETURNS Datetime AS
BEGIN
--USAGE select dbo.MinDate('20120405',null,null,'20110305',null)
DECLARE @Output datetime;

WITH Datelist_CTE(DT)
AS (
        SELECT @Date1 AS DT WHERE @Date1 is not NULL UNION
        SELECT @Date2 AS DT WHERE @Date2 is not NULL UNION
        SELECT @Date3 AS DT WHERE @Date3 is not NULL UNION
        SELECT @Date4 AS DT WHERE @Date4 is not NULL UNION
        SELECT @Date5 AS DT WHERE @Date5 is not NULL
   )
Select @Output=Min(DT) FROM Datelist_CTE

RETURN @Output
END
Lawrence
fonte
Acabei de perceber que você não precisa das Cláusulas WHERE, pois o MIN removerá os Nulos de qualquer maneira.
31413 Lawrence
2

Com base na lógica / código brilhante de mathematix e scottyc, eu envio:

DECLARE @a INT, @b INT, @c INT = 0

WHILE @c < 100
    BEGIN
        SET @c += 1
        SET @a = ROUND(RAND()*100,0)-50
        SET @b = ROUND(RAND()*100,0)-50
        SELECT @a AS a, @b AS b,
            @a - ( ABS(@a-@b) + (@a-@b) ) / 2 AS MINab,
            @a + ( ABS(@b-@a) + (@b-@a) ) / 2 AS MAXab,
            CASE WHEN (@a <= @b AND @a = @a - ( ABS(@a-@b) + (@a-@b) ) / 2)
            OR (@a >= @b AND @a = @a + ( ABS(@b-@a) + (@b-@a) ) / 2)
            THEN 'Success' ELSE 'Failure' END AS Status
    END

Embora o salto da função MIN do scottyc para a função MAX deva ter sido óbvio para mim, não foi, então resolvi fazer isso e o incluí aqui: SELECT @a + (ABS (@ b- @ a) + ( @ b- @ a)) / 2. Os números gerados aleatoriamente, embora não sejam prova, devem pelo menos convencer os céticos de que ambas as fórmulas estão corretas.

DaveX
fonte