SQL Serverには、.NETのMath.Maxのような2つの値をとるMax関数はありますか?


488

私はこのようなクエリを書きたいです:

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

しかし、これはMAX関数が機能する方法ではありませんよね?これは集約関数であるため、単一のパラメーターを想定し、すべての行のMAXを返します。

誰か私のやり方で知っていますか?


13
それは他のほとんどのデータベースにGREATEST関数として実装されています。SQLiteは、MAX集計で複数の列を許可することでサポートをエミュレートします。
OMGポニー


以下のmax(a、b)の解を見つけるときは、 "a"や "b"の構文または計算を繰り返すかどうかについての質問に注意してください。つまり、「b」が多くの構文を含む複雑な計算から導出される場合、「b」が1回だけ出現するソリューションを選択できます。たとえば、ソリューション「IIF(a> b、a、b)」は「b」を繰り返すことを意味します。これは構文的に醜いかもしれませんが、次のソリューションは「b」(および「a」)が1回だけ現れることを意味します:SELECT MAX(VALUE) FROM(SELECT a AS VALUE UNION SELECT b AS VALUE)AS T1
Andrew Jens

回答:


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 isnull(@ val2、@ val1)」に変更するだけの場合、値の1つがnullの場合、関数はnull以外の値を返します。それ以外の場合は、次のように機能します。ノーマル
クリストフ2008

1
他のデータ型についてはどうですか。たとえば、HigherIntegerArgumentとHigherDateTimeArgumentおよびHigherVarcharArgumentと...を書き込む必要がありますか?
onedaywhen

9
すべてのものはUDFをスカラーするため、これは信じられないほど遅くなります。代わりにインラインUDFを使用してください
AK

12
@xan実際に質問したとき、何が頭に浮かんだかわかりません。明らかに、多すぎません。とにかく答えてくれてありがとう。
トーマス

13
@Thomas義務的なミーム画像(決してあなたを対象とした違反はありません!)flickr.com/photos/16201371@N00/2375571206
xan

468

SQL Server 2008(またはそれ以上)を使用している場合は、これがより良いソリューションです。

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

すべてのクレジットと投票は、関連する質問「複数列のSQL MAX?」に対するSvenの回答に 行く必要があります。
ベストアンサー」だと私は言います。

  1. UNION、PIVOT、UNPIVOT、UDF、およびクレイジーロングのCASEステートメントでコードを複雑にする必要はありません。
  2. nullの処理の問題に悩まされることはなく、nullをうまく処理します。
  3. 「MAX」を「MIN」、「AVG」、または「SUM」と交換するのは簡単です。任意の集計関数を使用して、さまざまな列の集計を検索できます。
  4. 私が使用した名前(「AllPrices」や「Price」など)に限定されません。自分の名前を選んで、次の人のために読みやすく、理解しやすくすることができます。
  5. SQL Server 2008のderived_tablesを使用して、次のように複数の集計を見つけることができます:
    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 2012年

3
+1は、特に2つ以上のカラムを比較する場合に最適です。
JanW 2012

11
これは、スカラーを計算するだけでよいCASE WHENソリューションよりもパフォーマンスが低くなります。
テクマラ2013年

5
単純な構文は2つの値のMAXを決定するときにパフォーマンスヒットに値することは決してないかもしれませんが、それはより多くの値を持つ別の問題かもしれません。MAXの4つの値を取得する場合でも、CASE句は手動で生成すると長くなり、扱いにくく、エラーが発生しやすくなりますが、VALUES句は単純で明確なままです。
ティフロサウルス2014年

221

1行で実行できます。

-- 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 as MAX_OF_TWO"また、 "SELECT((@ val1 + @ val2)-ABS(@ val1- @ val2))/ 2 as MIN_OF_TWO 「。
トム

6
この方法では、合計がintに格納できる値よりも大きい場合、オーバーフローエラーが発生します。declare @ val1 int declare @ val2 int set @ val1 = 1500000000 set @ val2 = 1500000000 SELECT 0.5 *((@ val1 + @ val2)+ ABS(@
val1-

89
これは非常に「ダーティ」「トリック」です。コードをプログラミングする場合、目的を明示的に表現する必要がありますが、あなたの場合、難読化コンテストから取得したコードのように見えます。
greenoldman

24
「ダーティ」かもしれませんが、単純なSQL方言を持つデータベースの唯一のオプションである可能性があります。
splattne、2011年

12
マルシアスには同意しません。コメントで解決できる限り、コード自体は必ずしも目的を明示的に表す必要はありません。コード(またはどこでも)で複雑な数学の方程式を実行している場合、それを自己記述的にするのが難しい場合があります。より単純で理解しやすい部分に分割されている限り、それは正しいプログラミングです。
Rob

127

私はそうは思いません。先日これが欲しかった。私が得た最も近いものは:

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

4
これが私のお気に入りの方法です。あなたはオーバーフローの危険を冒すことはありません、そしてそれはsplattneの解決策(それはクールです)よりも不可解ではありません、そして私はUDFを作成する手間がありません。ケースは多くの状況で非常に便利です。
ランスフィッシャー

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

"o.NegotiatedPrice"の代わりに "(datediff(day、convert(datetime、adr_known_since、120)、getdate())-5)* 0.3"のような用語がある場合、このコードを繰り返す必要があります。用語の将来の変更は2回行う必要があります。min(x、y、...)タイプの関数の方がずっと良いでしょう
Daniel

87

IIF関数を試してみませんか(SQL Server 2012以降が必要です)

IIF(a>b, a, b)

それでおしまい。

(ヒント:どちらかがnullの場合は常にnull結果a>bがfalse になるため、どちらかがになることに注意してください。bこの場合は結果になります)


7
値の1つがの場合NULL、結果は常に2番目の値になります。
ジャフ2017

4
IIF()は、CASEステートメントの構文糖です。CASE条件のいずれかの値がNULLの場合、結果は2番目の値(ELSE)になります。
xxyzzy

ためである@xxyzzy NULL > 1234文は偽である

8
IIF(a>b, a, COALESCE(b,a))1つしか存在しない場合の値を示すため
mpag

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

このソリューションは、UDFを記述する必要なくDRYに準拠しているため(自分で繰り返さないでください)、このソリューションに+1を与えます。チェックする必要がある両方の値が他のsqlの結果である場合も素晴らしいです。たとえば、私の場合、2つのselect count(*)ステートメントのうち大きい方を見つけたいと考えています。
MikeKulls

1
このソリューションに頼らなければならないのは嫌ですが、SQL ServerでGREATESTまたはインラインMAXのネイティブサポートを追加するまでは、それがSQL Serverで行う最善の方法です。投稿していただきありがとうございます-あなたに+1してください!
SqlRyan

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の後です。最初の ">"比較はfalseを返し、いずれかの値が既にnullの場合はELSEに進みます。
Phil B

10

SQL Server 2012以降ではIIF、およびISNULL(またはCOALESCE)の組み合わせを使用して、最大2つの値を取得できます。
そのうちの1つが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

しかし、複数の値を合計する必要がある場合はどうでしょうか。
次に、VALUESの集約にCROSS APPLYすることをお勧めします。
これには、同時に他のものも計算できるという利点もあります。

例:

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

まだ2005年の人たちに愛を示すために+1。私がこの答えを見落とした方法がわかりません。カバーの下で、私はそれが2年後に投稿したものと同じように機能すると想像します。振り返ってみると、私はこれを認識し、その時点での新しい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あなたのいずれかの側のboolean_expression意志の原因がIIF返すようにfalse_value(とは対照的にNULL)。


他の値が負の場合、ソリューションはNULLをうまく処理できません。これはnullを返します
t-clausen.dk

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

編集マーク からのコメントの後に変更。彼が3つの値を持つロジックで正しく指摘したように、x> NULLまたはx <NULLは常にNULLを返す必要があります。つまり、不明な結果です。


1
ヌルは重要です。そして、それらを一貫して処理することが重要です。Is NULL> xに対する唯一の適切な答えはNULLです。
Mark Brackett

あなたは正しい、私はそれを反映するために私の答えを修正します。それを指摘してくれてありがとう
kristof '10 / 10/13

intとNULLを渡した場合、null以外の値を返す方が一般的であるため、関数はMax(x、y)とISNULL(x、y)の組み合わせとして機能しています。したがって、私は個人的に最後の行を次のように変更します:return ISNULL(@ val1、@ val2)-これはおそらくあなたが最初に始めなければならなかったものです:)
redcalx

@ the-locster、Markのコメントを参照
kristof 2010年

1
すべてのものはUDFをスカラーするため、これは信じられないほど遅くなります。代わりにインラインUDFを使用してください
AK

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)は間違っています。
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

説明についてはこちらの記事を参照してください:red-gate.com/simple-talk/sql/sql-training/...
トムArleth

2
リンクだけで必要な情報をコードに含めないでください。このリンクが1日で期限切れになり、回答が役に立たなくなることを想像してみてください。ですので、回答に直接エッセンシャル情報を追加してください。しかし、そのリンクを他の人がさらなる情報を調べるためのリソースとして提供できます。
L.ガタール

3

おっと、私はこの質問の複製を投稿しました...

答えは、Oracleの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

@ニールは正しいです(私はそれを難し​​い方法で学びました)、この種の問題を防ぐためにこの機能をどのように改善しますか?
Luca

3

nullを処理する必要があり、古いバージョンのMSSQLで動作するケースの例を次に示します。これは、一般的な例の1つにあるインライン関数に基づいています。

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

大きな数に関する上記の答えについては、加算/減算の前に乗算を行うことができます。少しかさばりますが、キャストは必要ありません。(私はスピードについて話すことはできませんが、それはまだかなり速いと思います)

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

への変更

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

キャストを避けたい場合は、少なくとも代替手段。


2

以下は、(Xinの回答に基づく)NULL処理を備えたIIFバージョンです。

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

これは、単純なNULL処理を使用した@Scott Langhamの回答です。

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ようなインラインの興味深い使用法はありますが、これがCASEまたはよりも単純であるかどうかはわかりません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では、次のように使用できます。

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.