Existe-t-il une fonction Max dans SQL Server qui prend deux valeurs, comme Math.Max dans .NET?
-
02-07-2019 - |
Question
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 comme ça que la fonction MAX
fonctionne, n'est-ce pas? Il s’agit d’une fonction d’agrégation qui attend un seul paramètre, puis renvoie le MAX de toutes les lignes.
Est-ce que quelqu'un sait comment le faire à ma façon?
La solution
Vous devez créer un User-Defined Function
si vous souhaitez une syntaxe similaire à 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 .
Le UDF
pourrait ressembler à 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'appelleriez comme si ...
SELECT o.OrderId, dbo.InlineMax(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o
Autres conseils
Si vous utilisez SQL Server 2008 (ou 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 doivent aller à réponse de Sven à une question connexe, & "; SQL MAX de plusieurs colonnes? quot;
Je dis que c'est la " meilleure réponse " parce que:
- Cela ne nécessite pas de compliquer votre code avec celui de UNION, celui de PIVOT, UNPIVOT, UDF et de longs procès CASE.
- Le problème de la gestion des valeurs NULL n’est pas en cause, il les gère parfaitement.
- 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.
- 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 faciliter la lecture et la compréhension du prochain gars.
- Vous pouvez rechercher plusieurs agrégats à l'aide des dérivées de type SQL Server 2008. :
SELECT MAX (a), MAX (b) FROM (VALEURS (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) AS MyTable ( a, b)
Peut être fait en une seule ligne:
-- the following expression calculates ==> max(@val1, @val2)
SELECT 0.5 * ((@val1 + @val2) + ABS(@val1 - @val2))
Modifier: Si vous avez affaire à de très grands nombres, vous devrez convertir les variables de valeur en bigint afin d'éviter un dépassement d'entier.
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
Pourquoi ne pas essayer la fonction IIF (requiert SQL Server 2012 et versions ultérieures)
IIF(a>b, a, b)
C'est tout.
(Conseil: soyez prudent avec soit null
, car 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)
DECLARE @MAX INT
@MAX = (SELECT MAX(VALUE)
FROM (SELECT 1 AS VALUE UNION
SELECT 2 AS VALUE) AS T1)
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
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 plusieurs colonnes. (Probablement plus utile lorsque le nombre de colonnes impliquées est supérieur)
;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
SQL Server 2012 a présenté IIF
:
SELECT
o.OrderId,
IIF( ISNULL( o.NegotiatedPrice, 0 ) > ISNULL( o.SuggestedPrice, 0 ),
o.NegotiatedPrice,
o.SuggestedPrice
)
FROM
Order o
Il est recommandé de traiter les valeurs NULL lors de l’utilisation de NULL
, car un boolean_expression
de chaque côté de votre false_value
force <=> à renvoyer le <=> (par opposition à <=>).
Je choisirais la solution fournie par kcrumley Il suffit de le modifier légèrement pour gérer les valeurs 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
MODIFIER Modifié après le commentaire de la Mark . Comme il l'a souligné correctement dans la logique à 3 valeurs x & Gt; NULL ou x & Lt; NULL devrait toujours retourner NULL. En d'autres termes, résultat inconnu.
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;
Oups, je viens de poster un dupe de cette question ...
La réponse est qu’il n’existe aucune 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
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
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
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 ne disposiez d'index de couverture pour les deux requêtes. Quoi qu'il en soit, 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
Voici une version IIF avec traitement NULL (basée 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, retourne celle qui n'est pas NULL (si les deux sont NULL, un NULL est renvoyé). Sinon, renvoyez le plus grand.
Même chose pour MIN.
IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a < b, a, b))
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
Vous pouvez faire quelque chose comme ceci:
select case when o.NegotiatedPrice > o.SuggestedPrice
then o.NegotiatedPrice
else o.SuggestedPrice
end
SELECT o.OrderID
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN
o.NegotiatedPrice
ELSE
o.SuggestedPrice
END AS Price
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
Pour la réponse ci-dessus concernant les grands nombres, vous pouvez effectuer la multiplication avant l'addition / la 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))
Modifications apportées à
SELECT @ val1 * 0.5 + @ val2 * 0.5 + ABS (@ val1 * 0,5 - @ val2 * 0,5)
au moins une alternative si vous voulez éviter le casting.
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
Pour SQL Server 2012:
SELECT
o.OrderId,
IIF( o.NegotiatedPrice >= o.SuggestedPrice,
o.NegotiatedPrice,
ISNULL(o.SuggestedPrice, o.NegiatedPrice)
)
FROM
Order o
Dans SQL Server 2012 ou version ultérieure, vous pouvez combiner IIF
et ISNULL
(ou COALESCE
) pour obtenir le maximum de 2 valeurs.
Même lorsque l'un d'entre eux est NULL.
IIF(col1 >= col2, col1, ISNULL(col2, col1))
Ou si vous souhaitez qu'il renvoie 0 lorsque les deux sont NULL
IIF(col1 >= col2, col1, COALESCE(col2, col1, 0))
Exemple d'extrait de code:
-- 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
Voici la réponse de @Scott Langham avec une gestion 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
select OrderId, (
select max([Price]) from (
select NegotiatedPrice [Price]
union all
select SuggestedPrice
) p
) from [Order]
Dans Presto, vous pouvez utiliser utiliser
SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
-- 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;
Répondant à 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 exemples de valeurs:
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