623

I want to write a query like this:

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

But this isn't how the MAX function works, right? It is an aggregate function so it expects a single parameter and then returns the MAX of all rows.

Does anyone know how to do it my way?

Johan
  • 74,508
  • 24
  • 191
  • 319
skb
  • 30,624
  • 33
  • 94
  • 146
  • 29
    That's implemented in most other databases as the `GREATEST` function; SQLite emulates support by allowing multiple columns in the `MAX` aggregate. – OMG Ponies Oct 21 '10 at 15:54
  • 8
    Possible duplicate of http://stackoverflow.com/questions/71022/sql-max-of-multiple-columns – Michael Freidgeim Jul 13 '11 at 11:56
  • 1
    When finding a solution for max(a, b) below keep in mind the question about whether you want the syntax or calculation for "a" and/or "b" to be repeated. I.e. if "b" is derived from a complex calculation involving lots of syntax then you may prefer a solution where "b" appears only once. E.g. the solution "IIF(a>b, a, b)" means repeating "b" – which might be syntactically ugly, however the following solution means "b" (and "a") appear only once: SELECT MAX(VALUE) FROM (SELECT a AS VALUE UNION SELECT b AS VALUE) AS T1 – Andrew Jens Mar 24 '17 at 02:48
  • Following up on **OMG Ponies**' good advice, in the DBMS I've been using, the function is `GREATER`, rather than `GREATEST`. So check the help for your DBMS, if you don't find one, try the other, or something similar. – Dan Konigsbach Mar 26 '21 at 20:27

31 Answers31

578

If you're using SQL Server 2008 (or above), then this is the better solution:

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

All credit and votes should go to Sven's answer to a related question, "SQL MAX of multiple columns?"
I say it's the "best answer" because:

  1. It doesn't require complicating your code with UNION's, PIVOT's, UNPIVOT's, UDF's, and crazy-long CASE statments.
  2. It isn't plagued with the problem of handling nulls, it handles them just fine.
  3. It's easy to swap out the "MAX" with "MIN", "AVG", or "SUM". You can use any aggregate function to find the aggregate over many different columns.
  4. You're not limited to the names I used (i.e. "AllPrices" and "Price"). You can pick your own names to make it easier to read and understand for the next guy.
  5. You can find multiple aggregates using SQL Server 2008's derived_tables like so:
    SELECT MAX(a), MAX(b) FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10) ) AS MyTable(a, b)
Community
  • 1
  • 1
MikeTeeVee
  • 18,543
  • 7
  • 76
  • 70
  • 37
    +1 only answer that doesn't require access to create procedure/functions! – Alex Apr 16 '12 at 11:25
  • 8
    Exactly the type of answer I was looking for. Using functions is slow and this will also work on dates, which is what I need. – Johann Strydom Jun 29 '12 at 11:08
  • 5
    +1 Works perfect, especially for more than 2 columns to be compared! – JanW Aug 30 '12 at 10:44
  • 13
    This is less performant than the CASE WHEN solution which only needs to compute a scalar. – tekumara Mar 31 '13 at 01:21
  • 1
    @tukushan I haven't noticed any performance issues using this in my queries (and I have used it a few times for populating my data warehouse and also in our online web reporting). With a CASE statement you will need to add NULL checks for it to work properly and we all know checking for NULL will ding you on performance if you're trying to milk every nanosecond. I honestly haven't taken the time to run a bunch of tests to compare, but I haven't run those tests because it's so lightning fast I haven't the need to look for a faster option. – MikeTeeVee Mar 31 '13 at 04:58
  • 1
    You might be right - I haven't extensively profiled this either. But I do notice the query plan generated for the above is more expensive than the CASE solution. – tekumara Mar 31 '13 at 20:42
  • 9
    While the simpler syntax is may never worth the performance hit when determining the MAX of 2 values, it may be a different matter with more values. Even when obtaining the MAX of 4 values the CASE clauses become long, clumsy and error prone if hand-generated while the VALUES clause remains simple and clear. – Typhlosaurus Jun 16 '14 at 15:05
  • 1
    Cannot be used if the result is needed in an aggregate function: `Cannot perform an aggregate function on an expression containing an aggregate or a subquery.` – Thorarin Mar 03 '15 at 06:57
  • @Thorarin Just use `@variable` to store the result then aggregate it – asakura89 Mar 09 '15 at 06:23
  • Accidentally and unknowingly clicked downvote and can't change it anymore. Sorry! The answer did help me out! – Hugo Delsing Aug 31 '15 at 11:15
  • And SQL Server 2005 compatible syntax in my unloved answer posted two years earlier! http://stackoverflow.com/a/3989370/73226 – Martin Smith Dec 06 '15 at 15:37
  • 2
    A word of caution - I used this in an UPDATE statement of the form SET x = (SELECT MAX(CopyCount) FROM (VALUES (x - 1),(0)) AS Copies(CopyCount)) and it performed horribly and occasionally caused gave me errors where the database could no longer update the row. – Evan M Jun 28 '16 at 20:05
  • This really should be a lot closer to the top, there are many other answers posted more recently that are direct duplicates of this, but they appear much close to the top of the list. – Chris Schaller Sep 02 '21 at 01:40
264

Can be done in one line:

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

Edit: If you're dealing with very large numbers you'll have to convert the value variables into bigint in order to avoid an integer overflow.

splattne
  • 102,760
  • 52
  • 202
  • 249
  • 25
    +1 I believe you have provided the most correct way. "SELECT ((@val1+@val2) + ABS(@val1-@val2))/2 as MAX_OF_TWO" Also remember, "SELECT ((@val1+@val2) - ABS(@val1-@val2))/2 as MIN_OF_TWO". – tom Jun 04 '09 at 20:00
  • 7
    This way will give an overflow error if the sum is greater than can be stored in an int: declare @val1 int declare @val2 int set @val1 = 1500000000 set @val2 = 1500000000 SELECT 0.5 * ((@val1 + @val2) + ABS(@val1 - @val2)) -- => overflow error – AakashM Jun 10 '09 at 13:02
  • 9
    I've never seen that before. Genius. – redcalx Oct 26 '09 at 13:18
  • 97
    This is extremely "dirty" "trick". When programming your code should explicitly express the aim, however in your case it looks like code taken from obfuscation contest. – greenoldman Jan 11 '11 at 09:50
  • 30
    It may be "dirty", but it could be the only option for databases with simple SQL dialects. – splattne Jan 11 '11 at 12:16
  • 14
    I disagree with marcias. Code doesnt necessarily itself need to explicitly express the aim, as long as comments allow one to work it out. If you are doing any complex mathematical equations in code (or anywhere) its sometimes kind of hard to make it self descriptive. As long as its broken up into simpler, easier to understand parts then that is correct programming. – Rob Mar 18 '11 at 04:32
  • 5
    Neat mathematical trick, but I cringe at the idea of doing floating point arithmetic to get a max of two integers. – KingErroneous May 18 '11 at 15:43
  • 3
    @King, it not floatingpoint per say, if you replace the `* 0.5` with a `div 2`, it's integer math again. This will also work very nice in other code, because you're avoiding a if-then jump and thus a branche mispredict (at the expense of a longer critical path obviously). – Johan Jun 30 '11 at 11:45
  • 2
    This will work for integers, etc., but not for other data types like datetime (which was my particular use case -- construct a column in the result set that at each row has the maximum (i.e., most recent) of two date columns). – Maxy-B Jan 24 '12 at 15:24
  • 7
    Old thread I know but this works for dates (it uses the formula above): `dateadd(dd, ((datediff(dd, 0, [Date1]) + datediff(dd, 0, [Date2])) + abs(datediff(dd, 0, [Date1]) - datediff(dd, 0, [Date2]))) / 2, 0)` – Adrian Torrie Jul 24 '13 at 01:03
  • 6
    Since it wasn't immediately obvious, this works because the `ABS` expression computes the magnitude of the difference between the min and max value. Adding that back on to the sum of the min and max value makes the sum twice the max value (which is always divisible by 2). Dividing by 2 returns the max value. I believe you could change this to achieve the functionality of `MIN` by subtracting the `ABS` expression instead of adding. – Michael Petito May 21 '15 at 17:18
  • 4
    Loss of precision when using float types means that you might not get the actual max value, only a number which is really close. – Trisped Jul 09 '15 at 20:14
  • @futureSPQR: CAST(0.5 * ((CAST(@val1 as float)+ CAST(@val2 AS float)) + ABS(cast(@val1 as float)- CAST(@val2 AS float))) AS datetime) – Stefan Steiger Jul 27 '16 at 11:45
  • Downvoted because it uses cleverness where a clean solution is available. It also produces a number that is not guaranteed to be equal to either column, due to arithmetic round-off errors. CASE statement should be used (or the equivalent IIF function if available). Greenoldman said it right. – xxyzzy Dec 11 '17 at 09:49
  • 2
    If either value is null, you get null. Probably not what's wanted. – Ross Presser Feb 13 '19 at 21:33
  • 5
    Someone worked out [the proof](https://math.stackexchange.com/a/429630) ♥ – Mr.Z Mar 18 '19 at 19:36
  • Definitely an elegant solution, which avoids comparison... Thanks! – bastio84 Jun 10 '20 at 10:00
187

You'd need to make a User-Defined Function if you wanted to have syntax similar to your example, but could you do what you want to do, inline, fairly easily with a CASE statement, as the others have said.

The UDF could be something like this:

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

... and you would call it like so ...

SELECT o.OrderId, dbo.InlineMax(o.NegotiatedPrice, o.SuggestedPrice) 
FROM Order o
SteveC
  • 15,808
  • 23
  • 102
  • 173
Kevin
  • 5,874
  • 3
  • 28
  • 35
  • 27
    I would support you solution, the only thing I would add is the support for NULL values. If you simply modify the final line: "return @value2" to read as: "return isnull(@val2,@val1)" then if one of the values is null the function will return the not null value, otherwise it will work as normal – kristof Sep 24 '08 at 10:32
  • 2
    What about other data types e.g. would I need to write a HigherIntegerArgument and a HigherDateTimeArgument and a HigherVarcharArgument and a ...? – onedaywhen Jun 10 '09 at 13:27
  • 12
    this will be incredibly slow, as all things scalar UDFs. Use inline UDFs instead – A-K Oct 21 '10 at 16:55
  • Can something similar be done to get the lowest value of the two? – Thomas Dec 23 '10 at 08:24
  • 13
    @xan I have no clue what went through my mind when I actually asked that question. Not too much, obviously. Thanks for the answer anyway. – Thomas Dec 16 '11 at 13:21
  • 1
    @Thomas No worries - I've asked the same kind of question too from time to time. Sometimes the brain just overthinks things... – xan Dec 19 '11 at 15:57
  • 14
    @Thomas Obligatory meme image (no offence intended to you in any way!) http://www.flickr.com/photos/16201371@N00/2375571206/ – xan Dec 19 '11 at 15:58
  • This solution has the advantage of working with any data type for which `>` is defined. – Maxy-B Jan 24 '12 at 15:32
  • Note that the edited solution handles nulls like Max(), as requested (Max returns the greatest non-null), but doesn't work the same way as simple > comparisons (which return null if either of the arguments are null). Used in a different context, this could lead to potentially counter-intuitive results, like saying that -999999999 is greater than null. I renamed the function so it no longer implies that it acts like "greater than". – Kevin Feb 07 '12 at 19:57
  • This answer doesn't work if the developer writing the query doesn't have permission to create user-defined functions and can't convince the DBAs to do so. I prefer the answers provided by D Nesmith and Mark Brackett. Although changing ISNULL to COALESCE would make D Nesmith's answer more portable. – dougp Oct 23 '20 at 19:43
145

Why not try IIF function (requires SQL Server 2012 and later)

IIF(a>b, a, b)

That's it.

(Extra hint: be careful about either a or b is null, as in this case the result of a>b will be false. So b will be the return result if either is null) (Also by system design, column null is not a good practice)

Xin
  • 33,823
  • 14
  • 84
  • 85
143

I don't think so. I wanted this the other day. The closest I got was:

SELECT
  o.OrderId,
  CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN o.NegotiatedPrice 
     ELSE o.SuggestedPrice
  END
FROM Order o
Scott Langham
  • 58,735
  • 39
  • 131
  • 204
  • 7
    This is my favorite method. You don't risk an overflow, and it's less cryptic than splattne's solution (which is cool btw), and I don't have the hassle of creating a UDF. case is very handy in many situations. – Lance Fisher Dec 23 '11 at 22:29
  • 1
    SELECT o.OrderId, CASE WHEN o.NegotiatedPrice > o.SuggestedPrice OR o.SuggestedPrice IS NULL THEN o.NegotiatedPrice ELSE o.SuggestedPrice END FROM Order o – mohghaderi Apr 20 '17 at 15:08
  • 1
    When instead of "o.NegotiatedPrice" you have rather a term like "(datediff(day, convert(datetime, adr_known_since, 120), getdate())-5)*0.3" you have to repeat this code. Any future changes to the term have to be done twice. A min(x, y, ...) type function would be much nicer – Daniel May 26 '20 at 12:25
  • This answer seems to be outdated: GREATEST exists in TSQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/logical-functions-greatest-transact-sql?view=sql-server-ver15#:~:text=%20Logical%20Functions%20-%20GREATEST%20%28Transact-SQL%29%20%201,of%20a%20data%20type%20that%20is...%20More%20 – cskwg Nov 04 '21 at 04:49
38
DECLARE @MAX INT
@MAX = (SELECT MAX(VALUE) 
               FROM (SELECT 1 AS VALUE UNION 
                     SELECT 2 AS VALUE) AS T1)
Martin Smith
  • 438,706
  • 87
  • 741
  • 845
jbeanky
  • 480
  • 4
  • 6
  • 1
    I give this solution a +1 because it conforms to DRY (don't repeat yourself) without the need to write a UDF. It's also great if both the values you need to check are the results of other sql, eg in my case I want to find the greater of 2 select count(*) statements. – MikeKulls Mar 22 '12 at 23:41
  • 2
    I hate that I have to resort to this solution, but it's for sure the best way to do it in SQL Server until they add native support for GREATEST or in-line MAX. Thanks for posting it - +1 to you! – SqlRyan May 04 '12 at 19:48
20

In SQL Server 2012 or higher, you can use a combination of IIF and ISNULL (or COALESCE) to get the maximum of 2 values.
Even when 1 of them is NULL.

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

Or if you want it to return 0 when both are NULL

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

Example snippet:

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

Result:

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

But if one needs the maximum of multiple columns?
Then I suggest a CROSS APPLY on an aggregation of the VALUES.

Example:

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

This has the extra benefit that this can calculate other things at the same time.

LukStorms
  • 28,916
  • 5
  • 31
  • 45
18

YES, THERE IS.

T-SQL (SQL Server 2022 (16.x)) now supports GREATEST/LEAST functions:

MAX/MIN as NON-aggregate function

This is now live for Azure SQL Database and SQL Managed Instance. It will roll into the next version of SQL Server.


Logical Functions - GREATEST (Transact-SQL)

This function returns the maximum value from a list of one or more expressions.

GREATEST ( expression1 [ ,...expressionN ] ) 

So in this case:

SELECT o.OrderId, GREATEST(o.NegotiatedPrice, o.SuggestedPrice)
FROM [Order] o;

db<>fiddle demo

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
16

Try this. It can handle more than 2 values

SELECT Max(v) FROM (VALUES (1), (2), (3)) AS value(v)
Chris Rogers
  • 1,773
  • 1
  • 15
  • 18
  • Super! I wrote a solution using `GREATEST` that runs on our AZURE SQL Server, but this solution also runs on my desktop SQL Server Express – mortb Aug 31 '21 at 18:24
  • This is the best solution. Especially if your values are derived from complex functions. – brenth Feb 22 '22 at 19:38
  • The value in the value list can be an expression, but need extra brackets: select max(v) from (values ((select max(a) from a_tab)), ((select max(b) from b_tab))) as value(v) – s-s Jan 17 '23 at 14:30
13

The other answers are good, but if you have to worry about having NULL values, you may want this variant:

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
  • 2
    The only required ISNULL is after the ELSE. The initial ">" comparison will return false and go to the ELSE if either of the values is null already. – Phil B Aug 30 '19 at 10:22
12

Sub Queries can access the columns from the Outer query so you can use this approach to use aggregates such as MAX across columns. (Probably more useful when there is a greater number of columns involved though)

;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
Martin Smith
  • 438,706
  • 87
  • 741
  • 845
  • 1
    Nice! It scales up very well. – greenoldman Jan 11 '11 at 09:52
  • 1
    +1 to show Love for those still on 2005. I don't know how I overlooked this answer. Under the covers, I imagine it performs just as well as what I posted 2-years later. In retrospect, I should have realized this and updated your answer to include the newer 2008 syntax at the time. Sorry, wish I could share my points with you now. – MikeTeeVee Dec 07 '15 at 00:48
  • 1
    @MikeTeeVee - Thanks! Yes under the covers the plan will be the same. But the `VALUES` syntax is nicer. – Martin Smith Dec 07 '15 at 20:05
  • good answer because it works across all versions including the new Azure DW/synapse, which doesn't support VALUES() – jkmelbs Aug 05 '20 at 01:27
9
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
Tom Arleth
  • 153
  • 1
  • 3
  • For explanation please consult this article: https://www.red-gate.com/simple-talk/sql/sql-training/table-value-constructors-in-sql-server-2008/ – Tom Arleth Sep 14 '18 at 11:42
  • 4
    Please don't include needed information to your code just by a link. Imagine that this link will expire one day and your answer will be useless then. So please go ahead and add the essentiell information directly in your answer. But you can still provide that link as a ressource for others to look further information up. – L. Guthardt Sep 14 '18 at 11:44
7

I would go with the solution provided by kcrumley Just modify it slightly to handle 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 Modified after comment from Mark. As he correctly pointed out in 3 valued logic x > NULL or x < NULL should always return NULL. In other words unknown result.

Community
  • 1
  • 1
kristof
  • 52,923
  • 24
  • 87
  • 110
  • 2
    Nulls are important. And it's important to handle them consistently. The only proper answer to Is NULL > x is NULL. – Mark Brackett Oct 10 '08 at 00:27
  • 1
    You are right, i will modify my answer to reflect that, thanks for pointing that out – kristof Oct 13 '08 at 07:48
  • 1
    If we pass an int and a NULL then I think it's more common to want the non-null value returned, so the function is acting as a combination of Max(x,y) and ISNULL(x,y). Hence I personally would change the last line to be: return ISNULL(@val1, @val2) - which admittedly is probably what you had to start with :) – redcalx Oct 26 '09 at 14:17
  • 1
    @the-locster, see comment by Mark – kristof Jan 17 '10 at 01:32
  • 1
    @the-locster, I agrre it is more common, but see the comment by Mark, nulls are important and as he points out "The only proper answer to Is NULL > x is NULL" – kristof Jan 17 '10 at 01:34
  • 3
    this will be incredibly slow, as all things scalar UDFs. Use inline UDFs instead – A-K Oct 21 '10 at 16:55
6

SQL Server 2012 introduced IIF:

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

Handling NULLs is recommended when using IIF, because a NULL on either side of your boolean_expression will cause IIF to return the false_value (as opposed to NULL).

SetFreeByTruth
  • 819
  • 8
  • 23
5

I probably wouldn't do it this way, as it's less efficient than the already mentioned CASE constructs - unless, perhaps, you had covering indexes for both queries. Either way, it's a useful technique for similar problems:

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
Mark Brackett
  • 84,552
  • 17
  • 108
  • 152
5

Oops, I just posted a dupe of this question...

The answer is, there is no built in function like Oracle's Greatest, but you can achieve a similar result for 2 columns with a UDF, note, the use of sql_variant is quite important here.

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

Posted this answer:

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
Community
  • 1
  • 1
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • 2
    Note: the GREATEST function implementation will match the oracle behavior for 2 params, if any param is null it will return null – Sam Saffron Oct 13 '08 at 11:41
  • 3
    You should be careful when using sql_variant. Your function will give an unexpected result in the following situation: SELECT dbo.greatest(CAST(0.5 AS FLOAT), 100) – Neil Jun 02 '11 at 15:43
  • @Neil is right (I learned it the hard way), how would you improve this function to prevent this kind of problems? – Luca Sep 20 '16 at 10:58
5

Its as simple as this:

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;
Uri Abramson
  • 6,005
  • 6
  • 40
  • 62
  • See @Neil comment to a previous answer SELECT dbo.InlineMax(CAST(0.5 AS FLOAT), 100) is wrong. – Luca Sep 20 '16 at 11:06
4

You can do something like this:

select case when o.NegotiatedPrice > o.SuggestedPrice 
then o.NegotiatedPrice
else o.SuggestedPrice
end
Per Hornshøj-Schierbeck
  • 15,097
  • 21
  • 80
  • 101
4
SELECT o.OrderID
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN
 o.NegotiatedPrice
ELSE
 o.SuggestedPrice
END AS Price
Wayne
  • 38,646
  • 4
  • 37
  • 49
4

Here's a case example that should handle nulls and will work with older versions of MSSQL. This is based on the inline function in one one of the popular examples:

case
  when a >= b then a
  else isnull(b,a)
end
scradam
  • 1,053
  • 11
  • 11
3

For the answer above regarding large numbers, you could do the multiplication before the addition/subtraction. It's a bit bulkier but requires no cast. (I can't speak for speed but I assume it's still pretty quick)

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

Changes to

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

at least an alternative if you want to avoid casting.

deepee1
  • 12,878
  • 4
  • 30
  • 43
3
 -- 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;
ashraf mohammed
  • 1,322
  • 15
  • 20
  • 1
    While interesting use of `VALUES` inline like that, I'm not sure this is simpler than `CASE` or `IFF`. I'd be interested to see how the performance of this solution stacks up against the other options though – Chris Schaller Jan 31 '20 at 22:43
  • @ChrisSchaller the interesting part with this usage of `VALUES`is that it provides an easy way to check more than one column for the `MAX` value. As for the performance, I do not know :) – mortb Aug 31 '21 at 18:40
  • @mortb this answer is a blatant rip off the earlier one from https://stackoverflow.com/a/52296106/1690217 I'm starting to see the value in this, but not sure I would call it "simple" We're effectively UNPIVOTing the values so that we can use an aggregate over the inner set. Its elegant, low code, but complex to understand. – Chris Schaller Sep 02 '21 at 01:33
2
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
sth
  • 222,467
  • 53
  • 283
  • 367
andrewc
  • 21
  • 1
2

Here is @Scott Langham's answer with simple NULL handling:

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
mohghaderi
  • 2,520
  • 1
  • 19
  • 12
2

Here is an IIF version with NULL handling (based on of Xin's answer):

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

The logic is as follows, if either of the values is NULL, return the one that isn't NULL (if both are NULL, a NULL is returned). Otherwise return the greater one.

Same can be done for MIN.

IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a < b, a, b))
jahu
  • 5,427
  • 3
  • 37
  • 64
2
select OrderId, (
    select max([Price]) from (
        select NegotiatedPrice [Price]
        union all
        select SuggestedPrice
    ) p
) from [Order]
error
  • 694
  • 4
  • 14
1

For SQL Server 2012:

SELECT 
    o.OrderId, 
    IIF( o.NegotiatedPrice >= o.SuggestedPrice,
         o.NegotiatedPrice, 
         ISNULL(o.SuggestedPrice, o.NegiatedPrice) 
    )
FROM 
    Order o
Steve Ford
  • 7,433
  • 19
  • 40
1

In its simplest form...

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
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
jsmink
  • 11
  • 1
1

Expanding on Xin's answer and assuming the comparison value type is INT, this approach works too:

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

This is a full test with example values:

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
Chris Porter
  • 3,627
  • 25
  • 28
-1

In MemSQL do the following:

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

In Presto you could use use

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
maxymoo
  • 35,286
  • 11
  • 92
  • 119