web-dev-qa-db-fra.com

Existe-t-il une fonction Max dans SQL Server qui prend deux valeurs, comme Math.Max ​​dans .NET?

Je veux écrire une requête comme celle-ci:

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

Mais ce n'est pas ainsi que fonctionne la fonction MAX, n'est-ce pas? Il s’agit d’une fonction d’agrégat. Elle attend donc un paramètre unique, puis renvoie le MAX de toutes les lignes. 

Est-ce que quelqu'un sait comment faire à ma façon?

405
skb

Vous devez créer un User-Defined Function si vous souhaitez une syntaxe semblable à celle de votre exemple, mais pouvez-vous faire ce que vous voulez faire, en ligne, assez facilement avec une instruction CASE, comme l'ont dit d'autres.

La UDF pourrait être quelque chose comme ceci:

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

... et vous l'appeleriez comme si ...

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

Si vous utilisez SQL Server 2008 (ou une version ultérieure), voici la meilleure solution:

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

Tous les crédits et votes devraient aller à La réponse de Sven à une question connexe, "SQL MAX de plusieurs colonnes?"
Je dis que c'est la "meilleure réponse" parce que:

  1. Cela ne nécessite pas de compliquer votre code avec les commandes UNION, PIVOT, UNPIVOT, UDF et CAS-Long.
  2. Le problème de la gestion des valeurs NULL n’est pas en cause, il les gère parfaitement.
  3. Il est facile d'échanger le "MAX" avec "MIN", "AVG" ou "SUM". Vous pouvez utiliser n'importe quelle fonction d'agrégat pour rechercher l'agrégat sur plusieurs colonnes différentes.
  4. Vous n'êtes pas limité aux noms que j'ai utilisés (c'est-à-dire "AllPrices" et "Price"). Vous pouvez choisir vos propres noms pour le rendre plus facile à lire et à comprendre pour le prochain gars.
  5. Vous pouvez trouver plusieurs agrégats à l'aide de Derived_tables like de SQL Server 2008:
    SELECT MAX (a), MAX (b) FROM (VALEURS (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) AS MyTable (a, b)
400
MikeTeeVee

Peut être fait en une seule ligne:

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

Edit: Si vous avez des nombres très grands, vous devrez convertir les variables de valeur en bigint afin d'éviter un dépassement d'entier. 

203
splattne

Je ne pense pas. Je voulais cela l'autre jour. Le plus proche que j'ai eu était:

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

Pourquoi ne pas essayerIIFfunction (requiert SQL Server 2012 et versions ultérieures)

IIF(a>b, a, b)

C'est tout.

(Astuce: soyez prudent à propos de l'un ou l'autre serait null, puisque le résultat de a>b sera faux chaque fois que l'un ou l'autre est nul. Donc, b sera le résultat dans ce cas)

54
Xin Wang
DECLARE @MAX INT
@MAX = (SELECT MAX(VALUE) 
               FROM (SELECT 1 AS VALUE UNION 
                     SELECT 2 AS VALUE) AS T1)
30
jbeanky

Les autres réponses sont bonnes, mais si vous devez vous inquiéter d'avoir des valeurs NULL, vous voudrez peut-être cette variante:

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
11
D Nesmith

Les sous-requêtes peuvent accéder aux colonnes de la requête externe afin que vous puissiez utiliser cette approche pour utiliser des agrégats tels que MAX sur des colonnes. (Probablement plus utile quand un plus grand nombre de colonnes est impliqué)

;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
8
Martin Smith

SQL Server 2012 introduit IIF :

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

Le traitement des valeurs NULL est recommandé lors de l'utilisation de IIF, car une NULL de chaque côté de votre boolean_expression fera en sorte que IIF renvoie le false_value (par opposition à NULL). 

6
SetFreeByTruth

Je voudrais aller avec la solution fournie par kcrumley Il suffit de le modifier légèrement pour gérer 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

EDIT Modifié après un commentaire de Mark . Comme il l'a correctement souligné dans la logique à 3 valeurs, x> NULL ou x <NULL doit toujours renvoyer NULL. En d'autres termes, résultat inconnu.

5
kristof

C'est aussi simple que cela: 

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;
4
Uri Abramson

Oups, je viens de poster un dupe de cette question ... 

La réponse est qu'il n'existe pas de fonction intégrée telle que Oracle's Greatest , mais vous pouvez obtenir un résultat similaire pour 2 colonnes avec un fichier UDF. Notez que l'utilisation de sql_variant est très importante ici.

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

Posté cette réponse: 

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
3
Sam Saffron

Voici un exemple de cas qui devrait gérer les valeurs NULL et fonctionnera avec les anciennes versions de MSSQL. Ceci est basé sur la fonction inline dans l'un des exemples populaires:

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

Je ne le ferais probablement pas de cette façon, car il est moins efficace que les constructions CASE déjà mentionnées - à moins que, peut-être, vous ayez des index de couverture pour les deux requêtes. De toute façon, c'est une technique utile pour des problèmes similaires:

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
Mark Brackett
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
2
Tom Arleth

Voici une version IIF avec un traitement NULL (basé sur la réponse de Xin):

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

La logique est la suivante: si l'une des valeurs est NULL, renvoyez celle qui n'est pas NULL (si les deux sont NULL, un NULL est renvoyé) Sinon, retournez le plus grand.

La même chose peut être faite pour MIN.

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

Dans sa forme la plus simple ...

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
jsmink

Vous pouvez faire quelque chose comme ça:

select case when o.NegotiatedPrice > o.SuggestedPrice 
then o.NegotiatedPrice
else o.SuggestedPrice
end
1
SELECT o.OrderID
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN
 o.NegotiatedPrice
ELSE
 o.SuggestedPrice
END AS Price
1
Wayne

Pour la réponse ci-dessus concernant les grands nombres, vous pouvez effectuer la multiplication avant l’addition/soustraction. C'est un peu plus volumineux mais ne nécessite aucun casting. (Je ne peux pas parler pour la vitesse mais je suppose que c'est quand même assez rapide) 

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

Changements à

SELECT @ val1 * 0.5 + @ val2 * 0.5 + ABS (@ val1 * 0,5 - @ val2 * 0,5)

au moins une alternative si vous voulez éviter le casting. 

1
deepee1

Pour SQL Server 2012:

SELECT 
    o.OrderId, 
    IIF( o.NegotiatedPrice >= o.SuggestedPrice,
         o.NegotiatedPrice, 
         ISNULL(o.SuggestedPrice, o.NegiatedPrice) 
    )
FROM 
    Order o
1
Steve Ford
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
andrewc

Dans Presto, vous pouvez utiliser

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
0
maxymoo
 -- 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;
0
ashraf mohammed

En développant la réponse de Xin et en supposant que le type de valeur de comparaison est INT, cette approche fonctionne également:

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

Ceci est un test complet avec des valeurs d'exemple:

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
Chris Porter
select OrderId, (
    select max([Price]) from (
        select NegotiatedPrice [Price]
        union all
        select SuggestedPrice
    ) p
) from [Order]
0
error

Voici la réponse de @Scott Langham avec une manipulation simple de 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
mohghaderi

Dans SQL Server 2012 ou version ultérieure, vous pouvez utiliser une combinaison de IIF et ISNULL (ou COALESCE) pour obtenir le maximum de 2 valeurs.
Même lorsque l’un d’eux est NULL. 

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

Ou si vous voulez qu'il renvoie 0 lorsque les deux sont NULL

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

Exemple d'extrait:

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

Résultat:

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