Existe uma função Max no SQL Server que aceita dois valores como Math.Max ​​no .NET?


488

Eu quero escrever uma consulta como esta:

SELECT o.OrderId, MAX(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o

Mas não é assim que a MAXfunção funciona, certo? É uma função agregada, portanto, espera um único parâmetro e, em seguida, retorna o MAX de todas as linhas.

Alguém sabe como fazer do meu jeito?


13
Isso é implementado na maioria dos outros bancos de dados como a GREATESTfunção; O SQLite emula o suporte, permitindo várias colunas no MAXagregado.
OMG Ponies


Ao encontrar uma solução para o máximo (a, b) abaixo, lembre-se da pergunta sobre se você deseja que a sintaxe ou o cálculo para "a" e / ou "b" sejam repetidos. Ou seja, se "b" é derivado de um cálculo complexo que envolve muita sintaxe, você pode preferir uma solução em que "b" apareça apenas uma vez. Por exemplo, a solução "IIF (a> b, a, b)" significa repetir "b" - que pode ser sintaticamente feio, no entanto, a seguinte solução significa "b" (e "a") aparece apenas uma vez: SELECT MAX (VALUE) FROM (SELECIONE a AS VALUE UNION SELECIONE b AS VALUE) AS T1
Andrew Jens

Respostas:


158

Você precisaria fazer um User-Defined Functionse quisesse ter uma sintaxe semelhante ao seu exemplo, mas poderia fazer o que deseja fazer, em linha, com bastante facilidade com uma CASEdeclaração, como os outros disseram.

O UDFpoderia ser algo como isto:

create function dbo.InlineMax(@val1 int, @val2 int)
returns int
as
begin
  if @val1 > @val2
    return @val1
  return isnull(@val2,@val1)
end

... e você chamaria assim ...

SELECT o.OrderId, dbo.InlineMax(o.NegotiatedPrice, o.SuggestedPrice) 
FROM Order o

24
Eu daria suporte à sua solução, a única coisa que gostaria de adicionar é o suporte a valores NULL. Se você simplesmente modificar a linha final: "return @ value2" para ler como: "return isnull (@ val2, @ val1)", se um dos valores for nulo, a função retornará o valor not null, caso contrário, funcionará como normal
kristof 24/09/08

1
E quanto a outros tipos de dados, por exemplo, eu precisaria escrever um HigherIntegerArgument e um HigherDateTimeArgument e um HigherVarcharArgument e um ...?
onedaywhen

9
isso será incrivelmente lento, como todas as coisas UDFs escalares. Em vez disso, use UDFs embutidos
AK

12
@xan Não tenho idéia do que passou pela minha cabeça quando realmente fiz essa pergunta. Não muito, obviamente. Obrigado pela resposta de qualquer maneira.
Thomas

13
@Thomas Obrigatória imagem em meme (sem ofensa para você de forma alguma!) Flickr.com/photos/16201371@N00/2375571206
xan

468

Se você estiver usando o SQL Server 2008 (ou superior), esta é a melhor solução:

SELECT o.OrderId,
       (SELECT MAX(Price)
        FROM (VALUES (o.NegotiatedPrice),(o.SuggestedPrice)) AS AllPrices(Price))
FROM Order o

Todo crédito e votos devem ir para a resposta de Sven para uma pergunta relacionada, "SQL MAX de várias colunas?"
Eu digo que é a " melhor resposta " porque:

  1. Não é necessário complicar o seu código com as declarações UNION, PIVOT, UNPIVOT, UDF e CASE de longa duração.
  2. Não é atormentado pelo problema de lidar com nulos, ele os trata muito bem.
  3. É fácil trocar o "MAX" por "MIN", "AVG" ou "SUM". Você pode usar qualquer função agregada para encontrar o agregado em muitas colunas diferentes.
  4. Você não está limitado aos nomes que usei (por exemplo, "AllPrices" e "Price"). Você pode escolher seus próprios nomes para facilitar a leitura e a compreensão do próximo cara.
  5. Você pode encontrar várias agregações usando as tabelas derivadas do SQL Server 2008, como:
    SELECT MAX (a), MAX (b) FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) COMO Minha Tabela (a, b)

27
Resposta apenas com +1 que não requer acesso para criar procedimentos / funções!
18712 Alex

6
Exatamente o tipo de resposta que eu estava procurando. O uso de funções é lento e isso também funciona em datas, que é o que eu preciso.
Johann Strydom

3
+1 Funciona perfeitamente, especialmente para comparar mais de 2 colunas!
JanW

11
Isso tem menos desempenho do que a solução CASE WHEN, que só precisa calcular um escalar.
Tekumara 31/03

5
Embora a sintaxe mais simples nunca valha o desempenho atingido ao determinar o máximo de 2 valores, pode ser uma questão diferente com mais valores. Mesmo ao obter o máximo de 4 valores, as cláusulas CASE tornam-se longas, desajeitadas e sujeitas a erros se geradas manualmente enquanto a cláusula VALUES permanece simples e clara.
Typhlosaurus

221

Pode ser feito em uma linha:

-- the following expression calculates ==> max(@val1, @val2)
SELECT 0.5 * ((@val1 + @val2) + ABS(@val1 - @val2)) 

Editar: se você estiver lidando com números muito grandes, precisará converter as variáveis ​​de valor em bigint para evitar um estouro de número inteiro.


18
+1 Eu acredito que você forneceu a maneira mais correta. "SELECT ((@ val1 + @ val2) + ABS (@ val1- @ val2)) / 2 como MAX_OF_TWO" Lembre-se de "SELECT ((@ val1 + @ val2) - ABS (@ val1- @ val2)) / 2 como MIN_OF_TWO "
Tom

6
Dessa forma, ocorrerá um erro de estouro se a soma for maior do que pode ser armazenada em um int: declare @ val1 int declare @ val2 int set @ val1 = 1500000000 set @ val2 = 1500000000 SELECT 0,5 * ((@ val1 + @ val2) + error => estouro - ABS (@ val1 - @ val2))
AakashM

89
Este é um truque extremamente "sujo". Ao programar, seu código deve expressar explicitamente o objetivo, no entanto, no seu caso, parece um código retirado de um concurso de ofuscação.
greenoldman

24
Pode estar "sujo", mas pode ser a única opção para bancos de dados com dialetos SQL simples.
splattne

12
Eu discordo de marcias. O código não necessariamente precisa expressar explicitamente o objetivo, desde que os comentários permitam que ele seja executado. Se você estiver fazendo equações matemáticas complexas no código (ou em qualquer outro lugar), às vezes é meio difícil torná-lo auto-descritivo. Desde que seja dividido em partes mais simples e fáceis de entender, essa é a programação correta.
Rob

127

Acho que não. Eu queria isso outro dia. O mais próximo que cheguei foi:

SELECT
  o.OrderId,
  CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN o.NegotiatedPrice 
     ELSE o.SuggestedPrice
  END
FROM Order o

4
Este é o meu método favorito. Você não corre o risco de transbordar, e é menos enigmático do que a solução de splattne (o que é legal), e eu não tenho o trabalho de criar uma UDF. caso é muito útil em muitas situações.
Lance Fisher

SELECIONAR o.OrderID, CASO QUANDO o.NegotiatedPrice> o.SuggestedPrice OU o.SuggestedPrice for nulo, o.NegotiatedPrice ELSE END o.SuggestedPrice da ordem o
mohghaderi

Quando, em vez de "o.NegotiatedPrice", você tem um termo semelhante a "(datado (dia, convertido (data, hora e data, adr_known_since, 120), getdate ()) - 5) * 0,3", é necessário repetir esse código. Quaisquer alterações futuras no termo devem ser feitas duas vezes. Uma função do tipo min (x, y, ...) seria muito melhor
Daniel

87

Por que não tentar a função IIF (requer o SQL Server 2012 e posterior)

IIF(a>b, a, b)

É isso aí.

(Dica: tenha cuidado com qualquer um deles null, pois o resultado de a>bserá falso sempre que um for nulo. Portanto b, será o resultado nesse caso)


7
Se um dos valores for NULL, o resultado será sempre o segundo.
jahu

4
IIF () é açúcar sintático para a instrução CASE. Se um dos valores da condicional CASE for NULL, o resultado será o segundo (ELSE).
xxyzzy

@xxyzzy é porque a NULL > 1234afirmação é falsa #
Xin

8
de modo IIF(a>b, a, COALESCE(b,a))a dar o valor quando só existe um
MPAG

32
DECLARE @MAX INT
@MAX = (SELECT MAX(VALUE) 
               FROM (SELECT 1 AS VALUE UNION 
                     SELECT 2 AS VALUE) AS T1)

Dou a esta solução um +1 porque está em conformidade com DRY (não se repita) sem a necessidade de escrever uma UDF. Também é ótimo se os dois valores que você precisa verificar são os resultados de outro sql, por exemplo, no meu caso, quero encontrar o maior de 2 instruções select count (*).
MikeKulls

1
Detesto ter que recorrer a essa solução, mas é com certeza a melhor maneira de fazê-lo no SQL Server até que eles adicionem suporte nativo ao GREATEST ou MAX in-line. Obrigado por publicá-lo - +1 para você!
SqlRyan

10

As outras respostas são boas, mas se você precisar se preocupar com valores NULL, convém esta variante:

SELECT o.OrderId, 
   CASE WHEN ISNULL(o.NegotiatedPrice, o.SuggestedPrice) > ISNULL(o.SuggestedPrice, o.NegotiatedPrice)
        THEN ISNULL(o.NegotiatedPrice, o.SuggestedPrice)
        ELSE ISNULL(o.SuggestedPrice, o.NegotiatedPrice)
   END
FROM Order o

1
O único ISNULL necessário é após o ELSE. A comparação inicial ">" retornará false e irá para o ELSE se um dos valores já for nulo.
Phil B

10

No SQL Server 2012 ou superior, você pode usar uma combinação de IIFe ISNULL(ou COALESCE) para obter o máximo de 2 valores.
Mesmo quando um deles é NULL.

IIF(col1 >= col2, col1, ISNULL(col2, col1)) 

Ou se você desejar retornar 0 quando ambos forem NULL

IIF(col1 >= col2, col1, COALESCE(col2, col1, 0)) 

Fragmento de exemplo:

-- use table variable for testing purposes
declare @Order table 
(
  OrderId int primary key identity(1,1),
  NegotiatedPrice decimal(10,2),
  SuggestedPrice decimal(10,2)
);

-- Sample data
insert into @Order (NegotiatedPrice, SuggestedPrice) values
(0, 1),
(2, 1),
(3, null),
(null, 4);

-- Query
SELECT 
     o.OrderId, o.NegotiatedPrice, o.SuggestedPrice, 
     IIF(o.NegotiatedPrice >= o.SuggestedPrice, o.NegotiatedPrice, ISNULL(o.SuggestedPrice, o.NegotiatedPrice)) AS MaxPrice
FROM @Order o

Resultado:

OrderId NegotiatedPrice SuggestedPrice  MaxPrice
1       0,00            1,00            1,00
2       2,00            1,00            2,00
3       3,00            NULL            3,00
4       NULL            4,00            4,00

Mas se é necessário somar vários valores?
Então, sugiro que CROSS APPLY a uma agregação dos VALUES.
Isso também tem o benefício de poder calcular outras coisas ao mesmo tempo.

Exemplo:

SELECT t.*
, ca.[Total]
, ca.[Maximum]
, ca.[Minimum]
, ca.[Average]
FROM SomeTable t
CROSS APPLY (
   SELECT 
    SUM(v.col) AS [Total], 
    MIN(v.col) AS [Minimum], 
    MAX(v.col) AS [Maximum], 
    AVG(v.col) AS [Average]
   FROM (VALUES (t.Col1), (t.Col2), (t.Col3), (t.Col4)) v(col)
) ca

8

As subconsultas podem acessar as colunas da consulta externa para que você possa usar essa abordagem para usar agregados, como MAXnas colunas. (Provavelmente é mais útil quando há um número maior de colunas envolvidas)

;WITH [Order] AS
(
SELECT 1 AS OrderId, 100 AS NegotiatedPrice, 110 AS SuggestedPrice UNION ALL
SELECT 2 AS OrderId, 1000 AS NegotiatedPrice, 50 AS SuggestedPrice
)
SELECT
       o.OrderId, 
       (SELECT MAX(price)FROM 
           (SELECT o.NegotiatedPrice AS price 
            UNION ALL SELECT o.SuggestedPrice) d) 
        AS MaxPrice 
FROM  [Order]  o

Agradável! Escala muito bem.
greenoldman

+1 para mostrar amor pelos que ainda estão em 2005. Não sei como ignorei essa resposta. Nos bastidores, imagino que ele tenha um desempenho tão bom quanto o que publiquei dois anos depois. Em retrospecto, eu deveria ter percebido isso e atualizado sua resposta para incluir a sintaxe mais recente de 2008 na época. Desculpe, gostaria de poder compartilhar meus pontos com você agora.
MikeTeeVee

@MikeTeeVee - Thanks! Sim, por baixo das cobertas, o plano será o mesmo. Mas a VALUESsintaxe é melhor.
Martin Smith

6

Introdução ao SQL Server 2012 IIF:

SELECT 
    o.OrderId, 
    IIF( ISNULL( o.NegotiatedPrice, 0 ) > ISNULL( o.SuggestedPrice, 0 ),
         o.NegotiatedPrice, 
         o.SuggestedPrice 
    )
FROM 
    Order o

O manuseio de NULLs é recomendado durante o uso IIF, porque um NULLdos lados boolean_expressionfará com IIFque você retorne false_value(ao contrário de NULL).


Sua solução não manipulará NULL bem quando o outro valor for negativo; isso retornará nulo
t-clausen.dk

5

Eu iria com a solução fornecida por kcrumley Apenas modifique-a um pouco para lidar com NULLs

create function dbo.HigherArgumentOrNull(@val1 int, @val2 int)
returns int
as
begin
  if @val1 >= @val2
    return @val1
  if @val1 < @val2
    return @val2

 return NULL
end

EDIT Modificado após o comentário de Mark . Como ele apontou corretamente em 3 lógicas valorizadas, x> NULL ou x <NULL devem sempre retornar NULL. Em outras palavras, resultado desconhecido.


1
Nulos são importantes. E é importante lidar com eles de forma consistente. A única resposta adequada para Is NULL> x é NULL.
22468 Mark Brackett

Você está certo, eu vou modificar minha resposta para refletir isso, obrigado por apontar isso
kristof

Se passarmos um int e um NULL, acho que é mais comum querer o valor não nulo retornado, portanto a função está atuando como uma combinação de Max (x, y) e ISNULL (x, y). Por isso eu, pessoalmente, iria mudar a última linha a ser: ISNULL retorno (@ val1, val2 @) - que reconhecidamente é provavelmente o que você tinha que começar com :)
redcalx

@ the-locster, ver comentário de Mark
kristof

1
isso será incrivelmente lento, como todas as coisas UDFs escalares. Em vez disso, use UDFs embutidos
AK

4

É tão simples quanto isto:

CREATE FUNCTION InlineMax
(
    @p1 sql_variant,
    @p2 sql_variant
)  RETURNS sql_variant
AS
BEGIN
    RETURN CASE 
        WHEN @p1 IS NULL AND @p2 IS NOT NULL THEN @p2 
        WHEN @p2 IS NULL AND @p1 IS NOT NULL THEN @p1
        WHEN @p1 > @p2 THEN @p1
        ELSE @p2 END
END;

Consulte o comentário do @Neil para uma resposta anterior SELECT dbo.InlineMax (CAST (0.5 AS FLOAT), 100) está errado.
Luca

4
SELECT o.OrderId,   
--MAX(o.NegotiatedPrice, o.SuggestedPrice)  
(SELECT MAX(v) FROM (VALUES (o.NegotiatedPrice), (o.SuggestedPrice)) AS value(v)) as ChoosenPrice  
FROM Order o

Para explicação consulte este artigo: red-gate.com/simple-talk/sql/sql-training/...
Tom Arleth

2
Não inclua as informações necessárias no seu código apenas por um link. Imagine que esse link expirará um dia e sua resposta será inútil. Portanto, vá em frente e adicione as informações essenciais diretamente na sua resposta. Mas você ainda pode fornecer esse link como um recurso para que outras pessoas procurem mais informações.
L. Guthardt 14/09/18

3

Ops, acabei de postar uma piada dessa pergunta ...

A resposta é: não existe uma função integrada como o Oracle's Greatest , mas você pode obter um resultado semelhante para duas colunas com uma UDF. Observe que o uso de sql_variant é bastante importante aqui.

create table #t (a int, b int) 

insert #t
select 1,2 union all 
select 3,4 union all
select 5,2

-- option 1 - A case statement
select case when a > b then a else b end
from #t

-- option 2 - A union statement 
select a from #t where a >= b 
union all 
select b from #t where b > a 

-- option 3 - A udf
create function dbo.GREATEST
( 
    @a as sql_variant,
    @b as sql_variant
)
returns sql_variant
begin   
    declare @max sql_variant 
    if @a is null or @b is null return null
    if @b > @a return @b  
    return @a 
end


select dbo.GREATEST(a,b)
from #t

Kristof

Publicou esta resposta:

create table #t (id int IDENTITY(1,1), a int, b int)
insert #t
select 1,2 union all
select 3,4 union all
select 5,2

select id, max(val)
from #t
    unpivot (val for col in (a, b)) as unpvt
group by id

1
Nota: a melhor implementação da função corresponderá ao comportamento do oracle para 2 parâmetros, se algum parâmetro for nulo, ele retornará nulo
Sam Saffron

2
Você deve ter cuidado ao usar sql_variant. Sua função fornecerá um resultado inesperado na seguinte situação: SELECT dbo.greatest (CAST (0.5 AS FLOAT), 100)
Neil

@ Neil está certo (eu aprendi da maneira mais difícil), como você melhoraria essa função para evitar esse tipo de problema?
Luca

3

Aqui está um exemplo de caso que deve tratar nulos e funcionará com versões mais antigas do MSSQL. Isso se baseia na função embutida em um dos exemplos populares:

case
  when a >= b then a
  else isnull(b,a)
end

2

Provavelmente não faria dessa maneira, pois é menos eficiente do que as construções CASE já mencionadas - a menos que, talvez, você tenha coberto índices para as duas consultas. De qualquer forma, é uma técnica útil para problemas semelhantes:

SELECT OrderId, MAX(Price) as Price FROM (
   SELECT o.OrderId, o.NegotiatedPrice as Price FROM Order o
   UNION ALL
   SELECT o.OrderId, o.SuggestedPrice as Price FROM Order o
) as A
GROUP BY OrderId

2

Para a resposta acima sobre números grandes, você pode fazer a multiplicação antes da adição / subtração. É um pouco mais volumoso, mas não requer elenco. (Não posso falar em velocidade, mas presumo que ainda seja muito rápido)

SELECT 0,5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2))

Muda para

SELECT @ val1 * 0.5 + @ val2 * 0.5 + ABS (@ val1 * 0.5 - @ val2 * 0.5)

pelo menos uma alternativa, se você quiser evitar a transmissão.


2

Aqui está uma versão IIF com manipulação NULL (com base na resposta do Xin):

IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a > b, a, b))

A lógica é a seguinte, se um dos valores for NULL, retorne aquele que não for NULL (se ambos forem NULL, um NULL será retornado). Caso contrário, retorne o maior.

O mesmo pode ser feito para MIN.

IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a < b, a, b))

1

Você pode fazer algo assim:

select case when o.NegotiatedPrice > o.SuggestedPrice 
then o.NegotiatedPrice
else o.SuggestedPrice
end

1
SELECT o.OrderID
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN
 o.NegotiatedPrice
ELSE
 o.SuggestedPrice
END AS Price

1
CREATE FUNCTION [dbo].[fnMax] (@p1 INT, @p2 INT)
RETURNS INT
AS BEGIN

    DECLARE @Result INT

    SET @p2 = COALESCE(@p2, @p1)

    SELECT
        @Result = (
                   SELECT
                    CASE WHEN @p1 > @p2 THEN @p1
                         ELSE @p2
                    END
                  )

    RETURN @Result

END

1

Na sua forma mais simples ...

CREATE FUNCTION fnGreatestInt (@Int1 int, @Int2 int )
RETURNS int
AS
BEGIN

    IF @Int1 >= ISNULL(@Int2,@Int1)
        RETURN @Int1
    ELSE
        RETURN @Int2

    RETURN NULL --Never Hit

END

1

Para o SQL Server 2012:

SELECT 
    o.OrderId, 
    IIF( o.NegotiatedPrice >= o.SuggestedPrice,
         o.NegotiatedPrice, 
         ISNULL(o.SuggestedPrice, o.NegiatedPrice) 
    )
FROM 
    Order o

1

Aqui está a resposta de @Scott Langham com manipulação simples de NULL:

SELECT
      o.OrderId,
      CASE WHEN (o.NegotiatedPrice > o.SuggestedPrice OR o.SuggestedPrice IS NULL) 
         THEN o.NegotiatedPrice 
         ELSE o.SuggestedPrice
      END As MaxPrice
FROM Order o

0
select OrderId, (
    select max([Price]) from (
        select NegotiatedPrice [Price]
        union all
        select SuggestedPrice
    ) p
) from [Order]

0
 -- Simple way without "functions" or "IF" or "CASE"
 -- Query to select maximum value
 SELECT o.OrderId
  ,(SELECT MAX(v)
   FROM (VALUES (o.NegotiatedPrice), (o.SuggestedPrice)) AS value(v)) AS MaxValue
  FROM Order o;

Embora o uso interessante de VALUESinline como esse, não tenho certeza se isso é mais simples que CASEou IFF. Eu ficaria interessado em ver como o desempenho desta solução se compara às outras opções
Chris Schaller

0

Expandindo a resposta de Xin e assumindo que o tipo de valor de comparação é INT, essa abordagem também funciona:

SELECT IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)

Este é um teste completo com valores de exemplo:

DECLARE @A AS INT
DECLARE @B AS INT

SELECT  @A = 2, @B = 1
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 2

SELECT  @A = 2, @B = 3
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 3

SELECT  @A = 2, @B = NULL
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 2    

SELECT  @A = NULL, @B = 1
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 1

0

No MemSQL, faça o seguinte:

-- DROP FUNCTION IF EXISTS InlineMax;
DELIMITER //
CREATE FUNCTION InlineMax(val1 INT, val2 INT) RETURNS INT AS
DECLARE
  val3 INT = 0;
BEGIN
 IF val1 > val2 THEN
   RETURN val1;
 ELSE
   RETURN val2;
 END IF; 
END //
DELIMITER ;

SELECT InlineMax(1,2) as test;

-1

No Presto você pode usar o uso

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.