Getting the minimum of two values in SQL
Asked Answered
A

12

242

I have two variables, one is called PaidThisMonth, and the other is called OwedPast. They are both results of some subqueries in SQL. How can I select the smaller of the two and return it as a value titled PaidForPast?

The MIN function works on columns, not variables.

Append answered 22/12, 2009 at 17:7 Comment(1)
If you're on Postgres or MySQL, skip down to @Gil_Margolin's answer.Apgar
T
147

Use Case:

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

As Inline table valued UDF

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

Usage:

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

ADDENDUM: This is probably best for when addressing only two possible values, if there are more than two, consider Craig's answer using Values clause.

Thoughtful answered 22/12, 2009 at 17:10 Comment(2)
better understandable syntax: return(select minValue = case when @@param1 < @@param2 then @@param1 else @@param2 end). Ok this may not be normalized, i don't know. But it's much more understandable and should be normalized.Pitre
Another reason to prefer @Craig's answer below is due to null handling. If the values being compared are nullable, and one of the values being compared is null, the switch-case shown might return null or the value, depending on the order of the WHEN test (unless you add use of ISNULL). Craig's approach will always prefer selection of the not-null value which seems more correct to me, at least in my current use-case in the comparison of nullable dates.Zakarias
W
217

SQL Server 2012 and 2014 supports IIF(cont,true,false) function. Thus for minimal selection you can use it like

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

While IIF is just a shorthand for writing CASE...WHEN...ELSE, it's easier to write.

Warrington answered 21/8, 2014 at 16:27 Comment(5)
IIF is just a syntactic sugar for CASE...WHEN...ELSE.Adkinson
Possibly yes. But easier to write.Hans
@MertGülsoy And easier to read, which should be at the top of everyone's priority list, right after correctness.Penutian
This should be the accepted answer. As a front-end developer, CASE...WHEN...ELSE is so awkward to read. IIF is way clearer and shorter.Prau
IIF(onlyRequireSQLServerGTE2012Support, thisAnswer, theOtherAnswer)Theme
S
176

The solutions using CASE, IIF, and UDF are adequate, but impractical when extending the problem to the general case using more than 2 comparison values. The generalized solution in SQL Server 2008+ utilizes a strange application of the VALUES clause:

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

Credit due to this website: 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

Shaniqua answered 25/8, 2015 at 16:40 Comment(7)
if you want the min non-zero: MIN(x*(case x when 0 then null else 1 end))Gibbeon
Except MartinC gave the same answer four years earlier, and actually showed it with more than two values...Lazare
Auspex, MartinC's answer is unrelated. This answer does not use unions.Shaniqua
Broken link in the credit due.Mycenaean
Would be a nice solution, however, limited: Cannot perform an aggregate function on an expression containing an aggregate or a subquery The only thing I care about here is to do min(a,b) and it must be short, simply for cleanlyness of the codePenutian
The subquery selecting from a VALUES constructor or a derived table using UNION ALL are basically the same thing. Both are unpivoting columns to rows and running an aggregate on them and will have the same plan.Planometer
i.e. to use it with the same example as this answer SELECT PaidForPast=(SELECT MIN(x) FROM (SELECT PaidThisMonth UNION ALL SELECT OwedPast) AS value(x))Planometer
T
147

Use Case:

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

As Inline table valued UDF

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

Usage:

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

ADDENDUM: This is probably best for when addressing only two possible values, if there are more than two, consider Craig's answer using Values clause.

Thoughtful answered 22/12, 2009 at 17:10 Comment(2)
better understandable syntax: return(select minValue = case when @@param1 < @@param2 then @@param1 else @@param2 end). Ok this may not be normalized, i don't know. But it's much more understandable and should be normalized.Pitre
Another reason to prefer @Craig's answer below is due to null handling. If the values being compared are nullable, and one of the values being compared is null, the switch-case shown might return null or the value, depending on the order of the WHEN test (unless you add use of ISNULL). Craig's approach will always prefer selection of the not-null value which seems more correct to me, at least in my current use-case in the comparison of nullable dates.Zakarias
M
58

For SQL Server 2022+ (or MySQL or PostgreSQL 9.3+), a better way is to use the LEAST and GREATEST functions.

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

With:

  • GREATEST(value [, ...]) : Returns the largest (maximum-valued) argument from values provided
  • LEAST(value [, ...]) Returns the smallest (minimum-valued) argument from values provided

Documentation links :

Mottle answered 9/6, 2016 at 21:57 Comment(6)
This also works in PostgreSQL (and it's exactly what I was looking for :) See: postgresql.org/docs/9.5/static/functions-conditional.htmlGabon
This is the best answer by far.Bylaw
@RobertoRodriguez it would be the best if the question had MySQL or PostgreSQL tagged as part of the question. The question was specifically about tsql so this answer does not help at all.Veach
this is not answer for MSSQLHitchcock
GREATEST and LEAST are promised to be available in Sql2022, so this answer was just over 6 years too early. ;)Switch
Works in Azure SQL (learn.microsoft.com/en-us/sql/t-sql/functions/…). Thanks!Grillwork
C
38

I just had a situation where I had to find the max of 4 complex selects within an update. With this approach you can have as many as you like!

You can also replace the numbers with aditional selects

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

More complex usage

 @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

I'm sure a UDF has better performance.

Celestinecelestite answered 8/3, 2011 at 13:29 Comment(1)
I like that one the most since it's basic SQL. Furthermore, UDFs are not necessarilly faster. For most column stores, each attribute (I assume you're also going to filter on the attributes) can be computed in parallel and just the qualifying set is unioned. So unions are not slow per se.Congressional
O
16

Here is a trick if you want to calculate maximum(field, 0):

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

returns 0 if field is negative, else, return field.

Ohmage answered 29/11, 2015 at 12:14 Comment(3)
So, to calculate minimum(@a, @b), you could use: SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2Schnur
and don't forget about type overflow ;)Elroyels
Is this save from floating-point precision standpoint? Is it certain, that the result will never be something close to zero but negative?Lapides
M
6

Use a CASE statement.

Example B in this page should be close to what you're trying to do:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Here's the code from the page:

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
Mosby answered 22/12, 2009 at 17:11 Comment(0)
B
2

This works for up to 5 dates and handles nulls. Just couldn't get it to work as an Inline function.

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;
Barbwire answered 4/7, 2013 at 11:46 Comment(1)
Just realised you don't need the WHERE Clauses as MIN will remove Nulls anyway.Barbwire
H
2

Building on the brilliant logic / code from mathematix and scottyc, I submit:

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;

Although the jump from scottyc's MIN function to the MAX function should have been obvious to me, it wasn't, so I've solved for it and included it here: SELECT @a + ( ABS(@b-@a) + (@b-@a) ) / 2. The randomly generated numbers, while not proof, should at least convince skeptics that both formulae are correct.

Homoousian answered 15/4, 2016 at 20:52 Comment(0)
D
1

Use a temp table to insert the range of values, then select the min/max of the temp table from within a stored procedure or UDF. This is a basic construct, so feel free to revise as needed.

For example:

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
Dumpcart answered 11/1, 2013 at 16:34 Comment(1)
What use is a procedure that gets the minimum of a set of hardcoded values? Furthermore, you have multiple syntax errors: * It seems () is not a valid parameter list. To declare a procedure with no parameters, it seems you need to not have brackets at all. * ' is not a comment marker in SQL. * You're missing the VALUES keyword. * You're missing the semicolons. It's important not to leave these off: #711183Aforetime
V
0
Select MIN(T.V) FROM (Select 1 as V UNION Select 2 as V) T
Velda answered 18/1, 2022 at 12:46 Comment(0)
J
-1
SELECT (WHEN first > second THEN second ELSE first END) the_minimal FROM table
Joannejoannes answered 31/1, 2023 at 13:53 Comment(3)
Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, can you edit your answer to include an explanation of what you're doing and why you believe it is the best approach?Caw
Also, that's especially important here, where there's existing answers that have been validated by the community, including one with nearly 200 upvotes. What sets your answer apart? Under what conditions would your answer be preferred?Caw
Not valid syntax. Missing CASE keyword. And if made valid syntax then repeats existing answersPlanometer

© 2022 - 2024 — McMap. All rights reserved.