Esiste una funzione Max in SQL Server che accetta due valori come Math.Max in .NET?
-
02-07-2019 - |
Domanda
Voglio scrivere una query come questa:
SELECT o.OrderId, MAX(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o
Ma non è così che funziona la funzione MAX
, giusto? È una funzione aggregata, quindi si aspetta un singolo parametro e quindi restituisce il MAX di tutte le righe.
Qualcuno sa come fare a modo mio?
Soluzione
Dovresti creare un User-Defined Function
se vuoi avere una sintassi simile al tuo esempio, ma potresti fare quello che vuoi fare, in linea, abbastanza facilmente con un'istruzione CASE
, come hanno detto gli altri .
Il UDF
potrebbe essere qualcosa del genere:
create function dbo.InlineMax(@val1 int, @val2 int)
returns int
as
begin
if @val1 > @val2
return @val1
return isnull(@val2,@val1)
end
... e lo chiameresti così ...
SELECT o.OrderId, dbo.InlineMax(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o
Altri suggerimenti
Se si utilizza SQL Server 2008 (o versioni successive), questa è la soluzione migliore:
SELECT o.OrderId,
(SELECT MAX(Price)
FROM (VALUES (o.NegotiatedPrice),(o.SuggestedPrice)) AS AllPrices(Price))
FROM Order o
Tutti i crediti e i voti dovrebbero andare a La risposta di Sven a una domanda correlata, " SQL MAX di più colonne? quot;
Dico che è la " migliore risposta " perché:
- Non richiede complicazioni del codice con UNION, PIVOT, Dichiarazioni UNPIVOT, UDF e CASE pazzesche.
- Non è afflitto dal problema di gestire i null, li gestisce bene.
- È facile sostituire il " MAX " con " MIN " ;, " AVG " ;, oppure " SUM " ;. Puoi utilizzare qualsiasi funzione di aggregazione per trovare l'aggregazione su molte colonne diverse.
- Non sei limitato ai nomi che ho usato (ovvero " AllPrices " e " Price "). Puoi scegliere i tuoi nomi per rendere più semplice la lettura e la comprensione per il prossimo ragazzo.
- Puoi trovare più aggregati utilizzando deriv_tables di SQL Server 2008 :
SELEZIONA MAX (a), MAX (b) DA (VALORI (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) AS MyTable ( a, b)
Può essere fatto in una riga:
-- the following expression calculates ==> max(@val1, @val2)
SELECT 0.5 * ((@val1 + @val2) + ABS(@val1 - @val2))
Modifica: Se hai a che fare con numeri molto grandi dovrai convertire le variabili di valore in bigint per evitare un overflow di numeri interi.
Non credo. L'ho voluto l'altro giorno. Il più vicino che ho avuto è stato:
SELECT
o.OrderId,
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN o.NegotiatedPrice
ELSE o.SuggestedPrice
END
FROM Order o
Perché non provare la funzione IIF (richiede SQL Server 2012 e versioni successive)
IIF(a>b, a, b)
Questo è tutto.
(Suggerimento: fare attenzione a entrambi sarebbe null
, poiché il risultato di a>b
sarà falso ogni volta che uno dei due sarà nullo. Quindi b
sarà il risultato in questo caso)
DECLARE @MAX INT
@MAX = (SELECT MAX(VALUE)
FROM (SELECT 1 AS VALUE UNION
SELECT 2 AS VALUE) AS T1)
Le altre risposte sono buone, ma se devi preoccuparti di avere valori NULL, potresti voler questa 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
Le query secondarie possono accedere alle colonne dalla query esterna in modo da poter utilizzare questo approccio per utilizzare aggregati come MAX
su più colonne. (Probabilmente più utile quando c'è un maggior numero di colonne coinvolte)
;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 ha introdotto IIF
:
SELECT
o.OrderId,
IIF( ISNULL( o.NegotiatedPrice, 0 ) > ISNULL( o.SuggestedPrice, 0 ),
o.NegotiatedPrice,
o.SuggestedPrice
)
FROM
Order o
Si consiglia di gestire i NULL quando si utilizza NULL
, poiché un boolean_expression
su entrambi i lati del false_value
farà sì che <=> restituisca <=> (anziché <=>).
Andrei con la soluzione fornita da kcrumley Modificalo leggermente per gestire i 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
Modifica Modificato dopo il commento di Mark . Come ha giustamente sottolineato in 3 valori logici x & Gt; NULL o x & Lt; NULL dovrebbe sempre restituire NULL. In altre parole, risultato sconosciuto.
È semplice come questo:
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;
Oops, ho appena pubblicato un dupe di questa domanda ...
La risposta è che non esiste una funzione integrata come Oracle's Greatest , ma puoi ottenere un risultato simile per 2 colonne con un UDF, nota, l'uso di sql_variant è abbastanza importante qui.
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
Inserita questa risposta:
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
Ecco un esempio di caso che dovrebbe gestire valori null e funzionerà con le versioni precedenti di MSSQL. Questo si basa sulla funzione incorporata in uno degli esempi popolari:
case
when a >= b then a
else isnull(b,a)
end
Probabilmente non lo farei in questo modo, poiché è meno efficiente dei già citati costrutti CASE - a meno che, forse, non avessi indici di copertura per entrambe le query. Ad ogni modo, è una tecnica utile per problemi simili:
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
Ecco una versione IIF con gestione NULL (basata sulla risposta di Xin):
IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a > b, a, b))
La logica è la seguente, se uno dei valori è NULL, restituisce quello che non è NULL (se entrambi sono NULL, viene restituito un NULL). Altrimenti restituisci quello più grande.
Lo stesso può essere fatto per 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
Puoi fare qualcosa del genere:
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
Per la risposta sopra riguardante numeri di grandi dimensioni, è possibile eseguire la moltiplicazione prima dell'addizione / sottrazione. È un po 'più ingombrante ma non richiede cast. (Non posso parlare per velocità ma presumo sia ancora piuttosto veloce)
SELEZIONA 0,5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2))
Modifiche a
SELEZIONA @ val1 * 0,5 + @ val2 * 0,5 + ABS (@ val1 * 0,5 - @ val2 * 0,5)
almeno un'alternativa se vuoi evitare il casting.
Nella sua forma più semplice ...
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
Per SQL Server 2012:
SELECT
o.OrderId,
IIF( o.NegotiatedPrice >= o.SuggestedPrice,
o.NegotiatedPrice,
ISNULL(o.SuggestedPrice, o.NegiatedPrice)
)
FROM
Order o
In SQL Server 2012 o versioni successive, è possibile utilizzare una combinazione di IIF
e ISNULL
(o COALESCE
) per ottenere un massimo di 2 valori.
Anche quando 1 di questi è NULL.
IIF(col1 >= col2, col1, ISNULL(col2, col1))
O se vuoi che ritorni 0 quando entrambi sono NULL
IIF(col1 >= col2, col1, COALESCE(col2, col1, 0))
Snippet di esempio:
-- 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
Risultato:
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
Ecco la risposta di @Scott Langham con una semplice gestione 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]
In Presto puoi usare use
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;
Espandendo la risposta di Xin e assumendo che il tipo di valore di confronto sia INT, anche questo approccio funziona:
SELECT IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
Questo è un test completo con valori di esempio:
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