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.