SQL Server中是否有一个Max函数采用两个值,如.NET中的Math.Max?


488

我想写这样的查询:

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

但这不是MAX函数的工作原理,对吗?它是一个聚合函数,因此需要一个参数,然后返回所有行的MAX。

有人知道我该怎么做吗?


13
在其他大多数数据库中,该GREATEST功能都已实现。SQLite通过允许MAX聚合中包含多个列来模拟支持。
OMG小马,2010年


在下面找到max(a,b)的解决方案时,请记住有关是否要重复“ a”和/或“ b”的语法或计算的问题。即,如果“ b”是从涉及大量语法的复杂计算中得出的,那么您可能更喜欢“ b”仅出现一次的解决方案。例如,解决方案“ IIF(a> b,a,b)”表示重复“ b” –语法上可能很难看,但是以下解决方案表示“ b”(和“ a”)仅出现一次:SELECT MAX(VALUE)从(选择AS值UNION选择b作为AS值)AS T1
Andrew Jens

Answers:


158

User-Defined Function如果您想拥有与示例相似的语法,则需要做一个a ,但是您是否可以CASE像其他人所说的那样,用一条语句轻松地内联地做您想做的事情。

UDF会是这样的:

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

...你会这样称呼它...

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

24
我会为您提供解决方案,我唯一要添加的就是对NULL值的支持。如果仅将最后一行:“ return @ value2”修改为:“ return isull(@ val2,@ val1)”,则如果值之一为null,则该函数将返回not null值,否则它将作为正常
克里斯托夫

1
那其他数据类型呢?例如,我需要写一个HigherIntegerArgument和HigherDateTimeArgument以及HigherVarcharArgument和...?
当日,2009年

9
由于所有标量UDF都是如此,所以这将非常慢。请使用内联UDF
AK 2010年

12
@xan我不知道当我真正问这个问题时,我想到了什么。不太明显。无论如何,谢谢您的回答。
托马斯

13
@Thomas强制模因图片(绝不以任何方式冒犯您!)flickr.com/photos/16201371@N00/2375571206
xan 2011年

467

如果您使用的是SQL Server 2008(或更高版本),则这是更好的解决方案:

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

所有的赞誉和投票都应该交给Sven对一个相关问题的答案:“多列的SQL MAX?”
我说这是“ 最佳答案 ”,因为:

  1. 它不需要将代码与UNION,PIVOT,UNPIVOT,UDF和疯狂的CASE语句复杂化。
  2. 它不存在处理null的问题,它可以很好地处理它们。
  3. 将“ MAX”替换为“ MIN”,“ AVG”或“ SUM”很容易。您可以使用任何聚合函数来查找许多不同列上的聚合。
  4. 您不仅限于我使用的名称(即“ AllPrices”和“ Price”)。您可以选择自己的名字,以方便下一个人阅读和理解。
  5. 您可以使用SQL Server 2008的衍生表找到多个聚合,如下所示:
    SELECT MAX(a),MAX(b)FROM(VALUES(1、2),(3、4),(5、6),(7、8), (9,10))AS MyTable(a,b)

27
仅+1不需要访问即可创建过程/功能的答案!
Alex

6
正是我在寻找的答案类型。使用函数的速度很慢,而且这也将在日期上起作用,这正是我所需要的。
Johann Strydom

3
+1完美,特别适合2个以上的列进行比较!
2012年

11
这比仅需要计算标量的CASE WHEN解决方案的性能差。
tekumara

5
虽然确定2个值的MAX时,最简单的语法可能永远不会对性能造成影响,但是对于更多的值,它可能是另一回事。即使获得了4个值的最大值,如果在VALUES子句保持简单明了的情况下手动生成,CASE子句也会变得很长,笨拙且容易出错。
暴龙2014年

220

可以一行完成:

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

编辑: 如果要处理非常大的数字,则必须将值变量转换为bigint,以避免整数溢出。


18
+1,我相信您提供了最正确的方法。“ SELECT((@ val1 + @ val2)+ ABS(@ val1- @ val2))/ 2为MAX_OF_TWO”还记得,“ SELECT((@ val1 + @ val2)-ABS(@ val1- @ val2))/ 2为MIN_OF_TWO ”。
汤姆

6
如果总和大于可以存储在int中的值,则这种方式将产生溢出错误:声明@ val1 int声明@ val2 int set @ val1 = 1500000000 set @ val2 = 1500000000 SELECT 0.5 *((@ val1 + @ val2)+ ABS(@ val1-@ val2))-=>溢出错误
AakashM,2009年

89
这是极其“肮脏”的“把戏”。编写代码时,应明确表达目标,但在您的情况下,它看起来像是来自混淆竞赛的代码。
greenoldman

24
它可能是“肮脏的”,但对于具有简单SQL方言的数据库来说,它可能是唯一的选择。
splattne

12
我不同意马西亚斯。只要注释允许一个人来实现,代码本身并不一定需要明确表达其目的。如果您正在代码中(或任何地方)执行任何复杂的数学方程式,有时很难使其具有自我描述性。只要将其分解为更简单,更容易理解的部分,就可以正确编程。
罗布

127

我不这么认为。前几天我想要这个。我最接近的是:

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

4
这是我最喜欢的方法。您不必冒溢出的风险,并且它比splattne的解决方案(这是很酷的解决方案)要神秘的多,而且我也没有创建UDF的麻烦。在许多情况下,这种情况非常方便。
Lance Fisher

选择o.OrderId,在o.NegotiatedPrice> o.SuggestedPrice或o.SuggestedPrice为NULL的情况下,然后o.NegotiatedPrice ELSE o.SuggestedPrice结束
案例

当代替“ o.NegotiatedPrice”而不是诸如“(datediff(day,convert(datetime,adr_known_since,120),getdate())-5)* 0.3”的术语时,您必须重复此代码。对该术语的任何将来更改都必须进行两次。一个min(x,y,...)类型的函数会好得多
Daniel

87

为什么不尝试IIF函数(需要SQL Server 2012及更高版本)

IIF(a>b, a, b)

而已。

(提示:请谨慎选择其中的一个null,因为a>b只要其中一个为null时,结果就会为false。因此b,在这种情况下,结果也将为false )


7
如果其中一个值为NULL,则结果将始终为第二个。
贾胡

4
IIF()是CASE语句的语法糖。如果CASE条件的任何一个值为NULL,则结果将是第二个(ELSE)。
xxyzzy

@xxyzzy这是因为NULL > 1234语句不正确
Xin

8
以便IIF(a>b, a, COALESCE(b,a))在只有一个存在时提供值
mpag

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

我将此解决方案设为+1,因为它符合DRY(无需重复说明),而无需编写UDF。如果您需要检查的两个值都是其他sql的结果,也很好,例如,在我的情况下,我想查找2个select count(*)语句中的较大者。
MikeKulls 2012年

1
我讨厌必须使用此解决方案,但是可以肯定的是,在SQL Server中添加对GREATEST或内联MAX的本机支持之前,这是在SQL Server中执行此操作的最佳方法。感谢您发布-为您+1!
SqlRyan 2012年

10

其他答案很好,但是如果您不得不担心具有NULL值,则可能需要此变体:

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
唯一需要的ISNULL在ELSE之后。如果两个值中的任何一个已经为null,则初始“>”比较将返回false并转到ELSE。
Phil B

10

在SQL Server 2012或更高版本中,可以使用IIFISNULL(或COALESCE)的组合来获取最多2个值。
即使其中之一为NULL。

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

或者如果您希望当两者均为NULL时返回0

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

示例片段:

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

结果:

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

但是,如果需要对多个值求和?
然后,我建议交叉应用“值”的汇总。
这还具有可以同时计算其他事物的好处。

例:

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

子查询可以访问“外部”查询中的列,因此您可以使用这种方法来使用聚合(例如MAX跨列)。(但是,当涉及更多列时,可能会更有用)

;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

真好!它可以很好地扩展。
greenoldman 2011年

+1以表示对仍然在2005年的人的爱。我不知道我如何忽略了这个答案。在幕后,我想它的表现和两年后发布的一样好。回想起来,我应该意识到这一点,并更新了您的答案,以包括当时的较新的2008语法。抱歉,希望我现在可以与您分享我的观点。
MikeTeeVee 2015年

@MikeTeeVee-谢谢!是的,该计划的内容是相同的。但是VALUES语法更好。
马丁·史密斯

6

SQL Server 2012引入了IIF

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

建议在使用时处理NULL IIF,因为NULL在您的两边的a boolean_expression都会导致IIF返回false_value(而不是NULL)。


当其他值为负值时,您的解决方案将无法很好地处理NULL,这将返回null
t-clausen.dk 2015年

5

我会使用kcrumley提供的解决方案, 只需对其稍作修改即可处理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

编辑Mark发表评论后修改。正如他在3个值逻辑中正确指出的那样,x> NULL或x <NULL应该总是返回NULL。换句话说,结果未知。


1
空值很重要。始终如一地处理它们很重要。NULL> x的唯一正确答案是NULL。
Mark Brackett's

您说得对,我将修改我的回答以反映这一点,感谢您指出这一点
克里斯托夫

如果我们传递一个int和一个NULL,那么我认为返回非null值是更常见的,因此该函数充当Max(x,y)和ISNULL(x,y)的组合。因此,我个人将最后一行更改为:return ISNULL(@ val1,@ val2)-坦白地说,这可能是您必须开始的内容:)
redcalx

@ the-locster,请参阅Mark的评论
kristof 2010年

1
由于所有标量UDF都是如此,所以这将非常慢。请使用内联UDF
AK 2010年

4

就这么简单:

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;

请参阅@Neil对先前答案的评论SELECT dbo.InlineMax(CAST(0.5 AS FLOAT),100)是错误的。

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

为了说明请参阅这篇文章:red-gate.com/simple-talk/sql/sql-training/...
汤姆Arleth

2
请不要仅通过链接在代码中包含所需的信息。想象一下,该链接将在一天后失效,那么您的答案将毫无用处。因此,请继续并直接在您的答案中添加essentiell信息。但是您仍然可以提供该链接作为资源,以供其他人查找更多信息。
L. Guthardt

3

糟糕,我刚刚发布了此问题虚假信息 ...

答案是,没有像Oracle's Greatest这样内置函数,但是您可以使用UDF对2列实现类似的结果,请注意,在这里sql_variant的使用非常重要。

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

克里斯托夫

发表此答案:

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
注意:GREATEST函数实现将匹配2个参数的oracle行为,如果任何参数为null,则它将返回null
Sam Saffron

2
使用sql_variant时应小心。在以下情况下,您的函数将产生意外的结果:SELECT dbo.greatest(CAST(0.5 AS FLOAT),100)
Neil

@Neil是正确的(我是很难学到的),如何改进此功能以防止此类问题?

3

这是一个应该处理空值并可以与旧版本的MSSQL一起使用的案例。这基于流行示例之一中的内联函数:

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

2

我可能不会这样做,因为它的效率比前面提到的CASE构造要低-除非,也许您已经涵盖了两个查询的索引。无论哪种方式,它都是解决类似问题的有用技术:

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

对于上面有关大数的答案,可以在加法/减法之前进行乘法。它有点笨重,但不需要演员。(我不能代表速度,但我认为它仍然相当快)

选择0.5 *((@ val1 + @ val2)+ ABS(@ val1-@ val2))

更改为

选择@ val1 * 0.5 + @ val2 * 0.5 + ABS(@ val1 * 0.5-@ val2 * 0.5)

如果您要避免投射,至少可以选择一种方法。


2

这是带有NULL处理的IIF版本(基于Xin的答案):

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

逻辑如下,如果两个值中的任何一个为NULL,则返回不为NULL的值(如果两个均为NULL,则返回NULL)。否则返回更大的一个。

MIN也可以这样做。

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


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

以最简单的形式...

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

对于SQL Server 2012:

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

1

这是@Scott Langham的简单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;

虽然VALUES像这样有趣地使用内联,但我不确定这是否比CASEor 更简单IFF。我很想看看该解决方案的性能如何与其他选项相提并论
Chris Schaller

0

扩展Xin的答案并假设比较值类型为INT,此方法也适用:

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

这是带有示例值的完整测试:

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

在MemSQL中执行以下操作:

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

在Presto中,您可以使用use

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.