¿Existe una función Max en SQL Server que tome dos valores como Math.Max ​​en .NET?


488

Quiero escribir una consulta como esta:

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

Pero no es así como MAXfunciona la función, ¿verdad? Es una función agregada, por lo que espera un único parámetro y luego devuelve el MAX de todas las filas.

¿Alguien sabe cómo hacerlo a mi manera?


13
Eso se implementa en la mayoría de las otras bases de datos como la GREATESTfunción; SQLite emula el soporte al permitir múltiples columnas en el MAXagregado.
OMG Ponis


Cuando encuentre una solución para max (a, b) a continuación, tenga en cuenta la pregunta sobre si desea que se repita la sintaxis o el cálculo de "a" y / o "b". Es decir, si "b" se deriva de un cálculo complejo que involucra mucha sintaxis, entonces puede preferir una solución donde "b" aparezca solo una vez. Por ejemplo, la solución "IIF (a> b, a, b)" significa repetir "b", que podría ser sintácticamente feo, sin embargo, la siguiente solución significa que "b" (y "a") aparecen solo una vez: SELECCIONAR MÁXIMO (VALOR) DESDE (SELECCIONE UN VALOR UNIÓN SELECCIONE b COMO VALOR) COMO T1
Andrew Jens

Respuestas:


158

Tendría que hacer un User-Defined Functionsi desea tener una sintaxis similar a su ejemplo, pero podría hacer lo que quiere hacer, en línea, con bastante facilidad con una CASEdeclaración, como han dicho los demás.

El UDFpodría ser algo como esto:

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

... y lo llamarías así ...

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

25
Apoyaría su solución, lo único que agregaría es el soporte para valores NULL. Si simplemente modifica la línea final: "return @ value2" para leer como: "return isnull (@ val2, @ val1)", entonces, si uno de los valores es nulo, la función devolverá el valor no nulo, de lo contrario funcionará como normal
kristof

1
¿Qué pasa con otros tipos de datos, por ejemplo, necesitaría escribir un HigherIntegerArgument y un HigherDateTimeArgument y un HigherVarcharArgument y un ...?
cuando el

99
Esto será increíblemente lento, ya que todas las UDF escalares. Utilice UDF en línea en su lugar
AK

12
@xan No tengo idea de lo que pasó por mi mente cuando realmente hice esa pregunta. No demasiado, obviamente. Gracias por la respuesta de todos modos.
Thomas

13
Meme imagen @Thomas Obligatorio (sin ánimo de ofender a usted de ninguna manera!) Flickr.com/photos/16201371@N00/2375571206
Xan

468

Si está utilizando SQL Server 2008 (o superior), esta es la mejor solución:

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

Todo el crédito y los votos deben ir a la respuesta de Sven a una pregunta relacionada, "¿SQL MAX de varias columnas?"
Yo digo que es la " mejor respuesta " porque:

  1. No requiere complicar su código con declaraciones UNION, PIVOT, UNPIVOT, UDF y CASE de larga duración.
  2. No está plagado con el problema de manejar nulos, los maneja bien.
  3. Es fácil cambiar el "MAX" por "MIN", "AVG" o "SUM". Puede usar cualquier función de agregado para encontrar el agregado en muchas columnas diferentes.
  4. No está limitado a los nombres que utilicé (es decir, "Todos los precios" y "Precio"). Puede elegir sus propios nombres para que sea más fácil de leer y comprender para el próximo chico.
  5. Puede encontrar múltiples agregados utilizando las tablas_de_productos de SQL Server 2008 de la siguiente manera:
    SELECCIONAR MÁXIMO (a), MÁX (b) DESDE (VALORES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) COMO MyTable (a, b)

27
¡+1 solo responde que no requiere acceso para crear procedimientos / funciones!
Alex

66
Exactamente el tipo de respuesta que estaba buscando. El uso de funciones es lento y esto también funcionará en fechas, que es lo que necesito.
Johann Strydom

3
+1 ¡Funciona perfecto, especialmente para comparar más de 2 columnas!
JanW

11
Esto es menos eficaz que la solución CASO CUANDO, que solo necesita calcular un escalar.
tekumara

55
Si bien la sintaxis más simple puede que nunca valga la pena el rendimiento al determinar el MAX de 2 valores, puede ser un asunto diferente con más valores. Incluso al obtener el MAX de 4 valores, las cláusulas CASE se vuelven largas, torpes y propensas a errores si se generan a mano, mientras que la cláusula VALUES sigue siendo simple y clara.
Typhlosaurus

221

Se puede hacer en una línea:

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

Editar: si se trata de números muy grandes, tendrá que convertir las variables de valor en bigint para evitar un desbordamiento de enteros.


18
+1 Creo que has proporcionado la forma más correcta. "SELECT ((@ val1 + @ val2) + ABS (@ val1- @ val2)) / 2 como MAX_OF_TWO" Recuerde también, "SELECT ((@ val1 + @ val2) - ABS (@ val1- @ val2)) / 2 como MIN_OF_TWO ".
tom

66
De esta forma, se generará un error de desbordamiento si la suma es mayor que la que se puede almacenar en un int: declare @ val1 int declare @ val2 int set @ val1 = 1500000000 set @ val2 = 1500000000 SELECT 0.5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2)) - => error de desbordamiento
AakashM

89
Este es un "truco" extremadamente "sucio". Al programar su código debe expresar explícitamente el objetivo, sin embargo, en su caso, parece un código tomado del concurso de ofuscación.
greenoldman

24
Puede estar "sucio", pero podría ser la única opción para bases de datos con dialectos SQL simples.
splattne

12
No estoy de acuerdo con marcias. El código no necesariamente necesita expresar explícitamente el objetivo, siempre y cuando los comentarios permitan que uno lo resuelva. Si está haciendo ecuaciones matemáticas complejas en código (o en cualquier lugar), a veces es difícil hacerlo autodescriptivo. Siempre y cuando se divida en partes más simples y fáciles de entender, esa es la programación correcta.
Rob

127

No lo creo. Quería esto el otro día. Lo más cerca que estuve fue:

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

44
Este es mi método favorito. No se arriesga a un desbordamiento, y es menos críptico que la solución de splattne (que es genial por cierto), y no tengo la molestia de crear un UDF. El caso es muy útil en muchas situaciones.
Lance Fisher

SELECCIONE o.OrderId, CASO CUANDO o.NegociadoPrecio> o.SuggestedPrice O o.SuggestedPrice ES NULO ENTONCES o.NegotiatedPrice ELSE o.SuggestedPrice END FROM Order o
mohghaderi

Cuando en lugar de "o.NegotiatedPrice" tienes un término como "(fechado (día, conversión (fecha y hora, adr_known_since, 120), getdate ()) - 5) * 0.3" tienes que repetir este código. Cualquier cambio futuro al término debe hacerse dos veces. Una función de tipo min (x, y, ...) sería mucho mejor
Daniel

87

¿Por qué no probar la función IIF (requiere SQL Server 2012 y posterior)

IIF(a>b, a, b)

Eso es.

(Sugerencia: tenga cuidado con cualquiera de los dos null, ya que el resultado a>bserá falso siempre que sea nulo. Así bserá el resultado en este caso)


77
Si uno de los valores es NULL, el resultado siempre será el segundo.
jahu

44
IIF () es azúcar sintáctico para la declaración CASE. Si cualquiera de los valores del condicional CASE es NULL, el resultado será el segundo (ELSE).
xxyzzy

@xxyzzy eso es porque la NULL > 1234declaración es falsa
Xin

8
por lo que IIF(a>b, a, COALESCE(b,a))para dar el valor cuando existe sólo uno
MPAG

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

Le doy a esta solución un +1 porque se ajusta a DRY (no se repita) sin la necesidad de escribir un UDF. También es genial si los dos valores que necesita verificar son los resultados de otros sql, por ejemplo, en mi caso quiero encontrar la mayor de 2 declaraciones select count (*).
MikeKulls

1
Odio tener que recurrir a esta solución, pero sin duda es la mejor manera de hacerlo en SQL Server hasta que agreguen soporte nativo para el MAYOR o el MAX en línea. Gracias por publicarlo - ¡+1 para ti!
SqlRyan

10

Las otras respuestas son buenas, pero si tiene que preocuparse por tener valores NULL, es posible que desee 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
El único ISNULL requerido es después de ELSE. La comparación inicial ">" devolverá falso e irá a ELSE si alguno de los valores ya es nulo.
Phil B

10

En SQL Server 2012 o superior, puede usar una combinación de IIFy ISNULL(o COALESCE) para obtener el máximo de 2 valores.
Incluso cuando 1 de ellos es NULL.

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

O si desea que devuelva 0 cuando ambos son NULL

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

Fragmento de ejemplo:

-- 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

Pero si uno necesita SUMAR múltiples valores?
Luego sugiero CRUZAR APLICAR a una agregación de los VALORES.
Esto también tiene el beneficio de que puede calcular otras cosas al mismo tiempo.

Ejemplo:

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

Las subconsultas pueden acceder a las columnas desde la consulta externa, por lo que puede utilizar este enfoque para utilizar agregados, como a MAXtravés de las columnas. (Probablemente sea más útil cuando haya una mayor cantidad de columnas involucradas)

;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

¡Agradable! Se escala muy bien.
Greenoldman

+1 para mostrar Amor por aquellos que aún están en 2005. No sé cómo pasé por alto esta respuesta. Debajo de las cubiertas, imagino que funciona tan bien como lo que publiqué 2 años después. En retrospectiva, debería haberme dado cuenta de esto y actualizar su respuesta para incluir la sintaxis más reciente de 2008 en ese momento. Lo siento, desearía poder compartir mis puntos con usted ahora.
MikeTeeVee

@MikeTeeVee - ¡Gracias! Sí, bajo las coberturas, el plan será el mismo. Pero la VALUESsintaxis es mejor.
Martin Smith

6

SQL Server 2012 introducido IIF:

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

Se recomienda manejar NULLs cuando se usa IIF, ya que NULLa ambos lados de la suya boolean_expressionhará IIFque devuelva false_value(en lugar de NULL).


Su solución no manejará NULL bien cuando el otro valor sea negativo, esto devolverá nulo
t-clausen.dk

5

Yo iría con la solución provista por kcrumley. Solo modifíquelo ligeramente para manejar NULL

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

EDITAR Modificado después del comentario de Mark . Como señaló correctamente en la lógica de 3 valores x> NULL o x <NULL siempre debe devolver NULL. En otras palabras, resultado desconocido.


1
Los nulos son importantes. Y es importante manejarlos de manera consistente. La única respuesta adecuada a Is NULL> x es NULL.
Mark Brackett

Tienes razón, modificaré mi respuesta para reflejar eso, gracias por señalarlo
kristof

Si pasamos un int y un NULL, creo que es más común querer que se devuelva el valor no nulo, por lo que la función actúa como una combinación de Max (x, y) e ISNULL (x, y). Por lo tanto, personalmente cambiaría la última línea para que sea: return ISNULL (@ val1, @ val2), que es probablemente lo que tenía que comenzar :)
redcalx

@ the-locster, ver comentario de Mark
kristof

1
Esto será increíblemente lento, ya que todas las cosas son UDF escalares. Utilice UDF en línea en su lugar
AK

4

Es tan simple como esto:

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;

Vea el comentario de @Neil a una respuesta anterior SELECT dbo.InlineMax (CAST (0.5 AS FLOAT), 100) está mal.
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 obtener una explicación, consulte este artículo: red-gate.com/simple-talk/sql/sql-training/…
Tom Arleth el

2
No incluya la información necesaria en su código solo mediante un enlace. Imagine que este enlace caducará algún día y su respuesta será inútil entonces. Así que adelante y agregue la información esencial directamente en su respuesta. Pero aún puede proporcionar ese enlace como un recurso para que otros busquen más información.
L. Guthardt el

3

Vaya, acabo de publicar un engaño de esta pregunta ...

La respuesta es que no hay una función integrada como Oracle's Greatest , pero puede lograr un resultado similar para 2 columnas con un UDF, tenga en cuenta que el uso de sql_variant es bastante importante aquí.

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

Publicado esta respuesta:

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: la implementación de la función MÁS GRANDE coincidirá con el comportamiento del oráculo para 2 parámetros, si algún parámetro es nulo, volverá nulo
Sam Saffron

2
Debe tener cuidado al usar sql_variant. Su función dará un resultado inesperado en la siguiente situación: SELECT dbo.greatest (CAST (0.5 AS FLOAT), 100)
Neil

@Neil tiene razón (lo aprendí por las malas), ¿cómo mejoraría esta función para evitar este tipo de problemas?
Luca

3

Aquí hay un ejemplo de caso que debería manejar nulos y funcionará con versiones anteriores de MSSQL. Esto se basa en la función en línea en uno de los ejemplos populares:

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

2

Probablemente no lo haría de esta manera, ya que es menos eficiente que las construcciones CASE ya mencionadas, a menos que, tal vez, haya tenido índices de cobertura para ambas consultas. De cualquier manera, es una técnica útil para problemas similares:

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 la respuesta anterior con respecto a los números grandes, puede hacer la multiplicación antes de la suma / resta. Es un poco más voluminoso pero no requiere yeso. (No puedo hablar por la velocidad, pero supongo que sigue siendo bastante rápido)

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

Cambios a

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

al menos una alternativa si quieres evitar el casting.


2

Aquí hay una versión IIF con manejo NULL (basado en la respuesta de Xin):

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

La lógica es la siguiente, si alguno de los valores es NULL, devuelve el que no es NULL (si ambos son NULL, se devuelve un NULL) De lo contrario, devuelva el mayor.

Lo mismo se puede hacer para MIN.

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

1

Puedes hacer algo como esto:

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

En su forma más simple ...

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 SQL Server 2012:

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

1

Aquí está la respuesta de @Scott Langham con un manejo simple 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;

Si bien el uso interesante de VALUESinline como ese, no estoy seguro de que sea más simple que CASEo IFF. Sin embargo
Chris Schaller

0

Ampliando la respuesta de Xin y suponiendo que el tipo de valor de comparación es INT, este enfoque también funciona:

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

Esta es una prueba completa con valores de ejemplo:

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

En MemSQL haga lo siguiente:

-- 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

En Presto puedes usar use

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.