Obtener el mínimo de dos valores en SQL

180

Tengo dos variables, una se llama PaidThisMonthy la otra se llama OwedPast. Ambos son resultados de algunas subconsultas en SQL. ¿Cómo puedo seleccionar el más pequeño de los dos y devolverlo como un valor titulado PaidForPast?

La MINfunción funciona en columnas, no en variables.

Malfist
fuente
1
Si estás en Postgres o MySQL, salta a la respuesta de @ Gil_Margolin.
Noumenon el

Respuestas:

127

Caso de uso:

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

Como tabla en línea valorada 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)

APÉNDICE: Esto probablemente sea mejor para abordar solo dos valores posibles, si hay más de dos, considere la respuesta de Craig usando la cláusula Values.

Charles Bretana
fuente
mejor sintaxis comprensible: return (seleccione minValue = case cuando @@ param1 <@@ param2 luego @@ param1 else @@ param2 end). Ok, esto puede no ser normalizado, no lo sé. Pero es mucho más comprensible y debería normalizarse.
Softlion
1
Otra razón para preferir la respuesta de @ Craig a continuación se debe al manejo nulo. Si los valores que se comparan son anulables, y uno de los valores que se comparan es nulo, el caso de cambio mostrado puede devolver nulo o el valor, dependiendo del orden de la prueba CUANDO (a menos que agregue el uso de ISNULL). El enfoque de Craig siempre preferirá la selección del valor no nulo que me parece más correcto, al menos en mi caso de uso actual en la comparación de fechas anulables.
Nij
148

SQL Server 2012 y 2014 admite la función IIF (cont, verdadero, falso). Por lo tanto, para una selección mínima, puede usarlo como

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

Si bien IIF es solo una abreviatura para escribir CASE...WHEN...ELSE, es más fácil de escribir.

Mert Gülsoy
fuente
8
IIFes solo un azúcar sintáctico para CASE...WHEN...ELSE.
Salman A
55
Posiblemente si. Pero más fácil de escribir.
Mert Gülsoy
1
@ MertGülsoy Y más fácil de leer, que debería estar en la parte superior de la lista de prioridades de todos, justo después de la corrección.
Daniel
118

Las soluciones que utilizan CASE, IIF y UDF son adecuadas, pero poco prácticas cuando se extiende el problema al caso general utilizando más de 2 valores de comparación. La solución generalizada en SQL Server 2008+ utiliza una aplicación extraña de la cláusula VALUES:

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

Crédito debido a este sitio web: 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
fuente
12
Esta es la mejor respuesta
FindOutIslamNow
si quieres que el min no sea cero:MIN(x*(case x when 0 then null else 1 end))
mpag
Excepto que MartinC dio la misma respuesta cuatro años antes, y en realidad la mostró con más de dos valores ...
Auspex
44
Auspex, la respuesta de MartinC no está relacionada. Esta respuesta no usa sindicatos.
Craig
30

Acabo de tener una situación en la que tuve que encontrar el máximo de 4 selecciones complejas dentro de una actualización. ¡Con este enfoque puedes tener tantos como quieras!

También puede reemplazar los números con selecciones adicionales

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

 @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

Estoy seguro de que un UDF tiene un mejor rendimiento.

MartinC
fuente
Me gusta más porque es SQL básico. Además, los UDF no son necesariamente más rápidos. Para la mayoría de los almacenes de columnas, cada atributo (supongo que también va a filtrar los atributos) se puede calcular en paralelo y solo se une el conjunto de calificación. Entonces los sindicatos no son lentos per se.
Bouncner
Sencillo e impresionante.
ashleedawg
22

Para MySQL o PostgreSQL 9.3+, una mejor manera es usar las funciones LEASTy 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

Con:

  • GREATEST(value [, ...]): Devuelve el argumento más grande (valor máximo) de los valores proporcionados
  • LEAST(value [, ...])Devuelve el argumento más pequeño (valor mínimo) de los valores proporcionados

Enlaces de documentación:

Gil Margolin
fuente
Esto también funciona en PostgreSQL (y es exactamente lo que estaba buscando :) Ver: postgresql.org/docs/9.5/static/functions-conditional.html
Albert Vaca Cintora
1
Esta es la mejor respuesta con diferencia.
Roberto Rodriguez
2
@RobertoRodriguez sería lo mejor si la pregunta tuviera MySQL o PostgreSQL etiquetado como parte de la pregunta. La pregunta era específicamente sobre tsql, por lo que esta respuesta no ayuda en absoluto.
Jmaurier
esta no es la respuesta para MSSQL
Mujah Maskey
13

Aquí hay un truco si desea calcular el máximo (campo, 0):

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

devuelve 0 si fieldes negativo, de lo contrario, devuelve field.

Mathix
fuente
3
Entonces, para calcular el mínimo (@a, @b), podría usar: SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2
scottyc
1
y no te olvides del desbordamiento de tipo;)
pkuderov
¿Es esto salvo desde el punto de vista de precisión de punto flotante? ¿Es cierto que el resultado nunca será algo cercano a cero sino negativo?
zuraff
6

Use una declaración CASE.

El ejemplo B en esta página debe estar cerca de lo que está intentando hacer:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Aquí está el código de la 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
fuente
2

Use una tabla temporal para insertar el rango de valores, luego seleccione el mínimo / máximo de la tabla temporal desde un procedimiento almacenado o UDF. Esta es una construcción básica, así que siéntase libre de revisar según sea necesario.

Por ejemplo:

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
usuario1970604
fuente
2

Esto funciona para hasta 5 fechas y maneja nulos. Simplemente no pude hacerlo funcionar como una función en línea.

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
fuente
Me acabo de dar cuenta de que no necesitas las cláusulas WHERE, ya que MIN eliminará los Null de todos modos.
Lawrence
2

Partiendo de la brillante lógica / código de Mathix y Scottyc, presento:

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

Aunque el salto de la función MIN de scottyc a la función MAX debería haber sido obvio para mí, no lo fue, así que lo resolví y lo incluí aquí: SELECT @a + (ABS (@ b- @ a) + ( @ b- @ a)) / 2. Los números generados aleatoriamente, aunque no son una prueba, al menos deberían convencer a los escépticos de que ambas fórmulas son correctas.

DaveX
fuente