Obtenir le minimum de deux valeurs dans SQL

180

J'ai deux variables, l'une s'appelle PaidThisMonthet l'autre s'appelle OwedPast. Ce sont tous les deux les résultats de certaines sous-requêtes en SQL. Comment puis-je sélectionner le plus petit des deux et le renvoyer sous la forme d'une valeur intitulée PaidForPast?

La MINfonction fonctionne sur les colonnes, pas sur les variables.

Malfist
la source
1
Si vous êtes sur Postgres ou MySQL, passez directement à la réponse de @ Gil_Margolin.
Noumenon

Réponses:

127

Cas d'utilisation:

   Select Case When @PaidThisMonth < @OwedPast 
               Then @PaidThisMonth Else @OwedPast End PaidForPast

Comme table en ligne valorisée UDF

CREATE FUNCTION Minimum
(@Param1 Integer, @Param2 Integer)
Returns Table As
Return(Select Case When @Param1 < @Param2 
                   Then @Param1 Else @Param2 End MinValue)

Usage:

Select MinValue as PaidforPast 
From dbo.Minimum(@PaidThisMonth, @OwedPast)

ADDENDA: Ceci est probablement le meilleur pour l'adressage de seulement deux valeurs possibles, s'il y en a plus de deux, considérez la réponse de Craig en utilisant la clause Values.

Charles Bretana
la source
syntaxe mieux compréhensible: return (sélectionnez minValue = case quand @@ param1 <@@ param2 puis @@ param1 sinon @@ param2 end). Ok ce n'est peut-être pas normalisé, je ne sais pas. Mais c'est beaucoup plus compréhensible et devrait être normalisé.
Softlion
1
Une autre raison de préférer la réponse de @ Craig ci-dessous est due au traitement nul. Si les valeurs comparées peuvent être nulles et que l' une des valeurs comparées est nulle, le cas de commutation affiché peut renvoyer null ou la valeur, selon l'ordre du test WHEN (sauf si vous ajoutez l'utilisation de ISNULL). L'approche de Craig préférera toujours la sélection de la valeur non nulle qui me semble plus correcte, du moins dans mon cas d'utilisation actuel dans la comparaison des dates Nullable.
Nij
148

SQL Server 2012 et 2014 prend en charge la fonction IIF (cont, true, false). Ainsi, pour une sélection minimale, vous pouvez l'utiliser comme

SELECT IIF(first>second, second, first) the_minimal FROM table

Alors que IIF n'est qu'un raccourci pour écrire CASE...WHEN...ELSE, il est plus facile à écrire.

Mert Gülsoy
la source
8
IIFest juste un sucre syntaxique pour CASE...WHEN...ELSE.
Salman A
55
Peut-être oui. Mais plus facile à écrire.
Mert Gülsoy
1
@ MertGülsoy Et plus facile à lire, qui devrait être en haut de la liste des priorités de tout le monde, juste après l'exactitude.
Daniel le
119

Les solutions utilisant CASE, IIF et UDF sont adéquates, mais peu pratiques lorsque l'on étend le problème au cas général en utilisant plus de 2 valeurs de comparaison. La solution généralisée dans SQL Server 2008+ utilise une application étrange de la clause VALUES:

SELECT
PaidForPast=(SELECT MIN(x) FROM (VALUES (PaidThisMonth),(OwedPast)) AS value(x))

Crédit dû à ce site Web: http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-maximum-value-from-some-columns-sql- serveur-t-sql.aspx

Craig
la source
12
C'est la meilleure réponse
FindOutIslamNow
si vous voulez le min non nul:MIN(x*(case x when 0 then null else 1 end))
mpag
Sauf que MartinC a donné la même réponse quatre ans plus tôt, et l'a en fait montré avec plus de deux valeurs ...
Auspex
4
Auspex, la réponse de MartinC est sans rapport. Cette réponse n'utilise pas les syndicats.
Craig
30

Je viens d'avoir une situation où je devais trouver le maximum de 4 sélections complexes dans une mise à jour. Avec cette approche, vous pouvez en avoir autant que vous le souhaitez!

Vous pouvez également remplacer les nombres par des sélections supplémentaires

select max(x)
 from (
 select 1 as 'x' union
 select 4 as 'x' union
 select 3 as 'x' union
 select 2 as 'x' 
 ) a

Utilisation plus complexe

 @answer = select Max(x)
           from (
                select @NumberA as 'x' union
                select @NumberB as 'x' union
                select @NumberC as 'x' union
                select (
                       Select Max(score) from TopScores
                       ) as 'x' 
     ) a

Je suis sûr qu'un UDF a de meilleures performances.

MartinC
la source
J'aime celui-là le plus car c'est du SQL basique. De plus, les UDF ne sont pas nécessairement plus rapides. Pour la plupart des magasins de colonnes, chaque attribut (je suppose que vous allez également filtrer sur les attributs) peut être calculé en parallèle et seul l'ensemble de qualification est unifié. Les syndicats ne sont donc pas lents en soi.
Bouncner
simple et génial.
ashleedawg
22

Pour MySQL ou PostgreSQL 9.3+, une meilleure façon est d'utiliser les fonctions LEASTet GREATEST.

SELECT GREATEST(A.date0, B.date0) AS date0, 
       LEAST(A.date1, B.date1, B.date2) AS date1
FROM A, B
WHERE B.x = A.x

Avec:

  • GREATEST(value [, ...]): Renvoie le plus grand argument (valeur maximale) à partir des valeurs fournies
  • LEAST(value [, ...])Renvoie le plus petit argument (valeur minimale) à partir des valeurs fournies

Liens de documentation:

Gil Margolin
la source
Cela fonctionne également dans PostgreSQL (et c'est exactement ce que je cherchais :) Voir: postgresql.org/docs/9.5/static/functions-conditional.html
Albert Vaca Cintora
1
C'est de loin la meilleure réponse.
Roberto Rodriguez
2
@RobertoRodriguez ce serait le mieux si la question avait MySQL ou PostgreSQL marqué comme faisant partie de la question. La question concernait spécifiquement tsql, donc cette réponse n'aide pas du tout.
Jmaurier
ce n'est pas la réponse pour MSSQL
Mujah Maskey
13

Voici une astuce si vous souhaitez calculer le maximum (champ, 0):

SELECT (ABS(field) + field)/2 FROM Table

renvoie 0 si fieldest négatif, sinon, retourne field.

mathématique
la source
3
Donc, pour calculer le minimum (@a, @b), vous pouvez utiliser:SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2
scottyc
1
et n'oubliez pas le débordement de type;)
pkuderov
Est-ce une sauvegarde du point de vue de la précision en virgule flottante? Est-il certain que le résultat ne sera jamais quelque chose de proche de zéro mais négatif?
zuraff
6

Utilisez une instruction CASE.

L'exemple B de cette page doit être proche de ce que vous essayez de faire:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Voici le code de la page:

USE AdventureWorks;
GO
SELECT   ProductNumber, Name, 'Price Range' = 
      CASE 
         WHEN ListPrice =  0 THEN 'Mfg item - not for resale'
         WHEN ListPrice < 50 THEN 'Under $50'
         WHEN ListPrice >= 50 and ListPrice < 250 THEN 'Under $250'
         WHEN ListPrice >= 250 and ListPrice < 1000 THEN 'Under $1000'
         ELSE 'Over $1000'
      END
FROM Production.Product
ORDER BY ProductNumber ;
GO
Mike Cole
la source
2

Utilisez une table temporaire pour insérer la plage de valeurs, puis sélectionnez le min / max de la table temporaire dans une procédure stockée ou UDF. Ceci est une construction de base, alors n'hésitez pas à réviser si nécessaire.

Par exemple:

CREATE PROCEDURE GetMinSpeed() AS
BEGIN

    CREATE TABLE #speed (Driver NVARCHAR(10), SPEED INT);
    '
    ' Insert any number of data you need to sort and pull from
    '
    INSERT INTO #speed (N'Petty', 165)
    INSERT INTO #speed (N'Earnhardt', 172)
    INSERT INTO #speed (N'Patrick', 174)

    SELECT MIN(SPEED) FROM #speed

    DROP TABLE #speed

END
user1970604
la source
2

Cela fonctionne jusqu'à 5 dates et gère les valeurs nulles. Je ne pouvais tout simplement pas le faire fonctionner en tant que fonction en ligne.

CREATE FUNCTION dbo.MinDate(@Date1 datetime = Null,
                            @Date2 datetime = Null,
                            @Date3 datetime = Null,
                            @Date4 datetime = Null,
                            @Date5 datetime = Null)
RETURNS Datetime AS
BEGIN
--USAGE select dbo.MinDate('20120405',null,null,'20110305',null)
DECLARE @Output datetime;

WITH Datelist_CTE(DT)
AS (
        SELECT @Date1 AS DT WHERE @Date1 is not NULL UNION
        SELECT @Date2 AS DT WHERE @Date2 is not NULL UNION
        SELECT @Date3 AS DT WHERE @Date3 is not NULL UNION
        SELECT @Date4 AS DT WHERE @Date4 is not NULL UNION
        SELECT @Date5 AS DT WHERE @Date5 is not NULL
   )
Select @Output=Min(DT) FROM Datelist_CTE

RETURN @Output
END
Lawrence
la source
Je viens de réaliser que vous n'avez pas besoin des clauses WHERE car MIN supprimera de toute façon les Nulls.
Lawrence le
2

En m'appuyant sur la logique / le code brillant de Mathematix et de Scottyc, je soumets:

DECLARE @a INT, @b INT, @c INT = 0

WHILE @c < 100
    BEGIN
        SET @c += 1
        SET @a = ROUND(RAND()*100,0)-50
        SET @b = ROUND(RAND()*100,0)-50
        SELECT @a AS a, @b AS b,
            @a - ( ABS(@a-@b) + (@a-@b) ) / 2 AS MINab,
            @a + ( ABS(@b-@a) + (@b-@a) ) / 2 AS MAXab,
            CASE WHEN (@a <= @b AND @a = @a - ( ABS(@a-@b) + (@a-@b) ) / 2)
            OR (@a >= @b AND @a = @a + ( ABS(@b-@a) + (@b-@a) ) / 2)
            THEN 'Success' ELSE 'Failure' END AS Status
    END

Bien que le passage de la fonction MIN de scottyc à la fonction MAX ait dû être évident pour moi, ce n'était pas le cas, alors je l'ai résolu et je l'ai inclus ici: SELECT @a + (ABS (@ b- @ a) + ( @ b- @ a)) / 2. Les nombres générés aléatoirement, bien qu'ils ne soient pas des preuves, devraient au moins convaincre les sceptiques que les deux formules sont correctes.

DaveX
la source