Uzyskiwanie co najmniej dwóch wartości w SQL

179

Mam dwie zmienne, jedna nazywa się PaidThisMonth, a druga nazywa OwedPast. Oba są wynikiem niektórych podkwerend w SQL. Jak mogę wybrać mniejszą z dwóch i zwrócić ją jako wartość zatytułowaną PaidForPast?

MINFunkcja działa na kolumnach, a nie zmienne.

Malfist
źródło
1
Jeśli korzystasz z Postgres lub MySQL, przejdź do odpowiedzi @ Gil_Margolin.
Noumenon,

Odpowiedzi:

127

Przypadek użycia:

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

Jako tabela Inline o wartości UDF

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

Stosowanie:

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

DODATEK: Jest to prawdopodobnie najlepsze w przypadku adresowania tylko dwóch możliwych wartości, jeśli są więcej niż dwie, rozważ odpowiedź Craiga przy użyciu klauzuli Values.

Charles Bretana
źródło
lepiej zrozumiała składnia: return (wybierz minValue = case when @@ param1 <@@ param2, a następnie @@ param1 else @@ param2 end). Ok, to może nie być znormalizowane, nie wiem. Ale jest to znacznie bardziej zrozumiałe i powinno zostać znormalizowane.
Softlion,
1
Innym powodem, dla którego wolę odpowiedź @ Craig poniżej, jest brak obsługi. Jeśli porównywane wartości są zerowalne, a jedna z porównywanych wartości ma wartość NULL, pokazany przypadek przełączenia może zwrócić wartość NULL lub wartość, w zależności od kolejności testu KIEDY (chyba że dodasz użycie ISNULL). Podejście Craiga zawsze będzie preferowało wybór wartości niepustej, która wydaje mi się bardziej poprawna, przynajmniej w moim obecnym przypadku użycia w porównaniu dat zerowych.
Nij
147

SQL Server 2012 i 2014 obsługuje funkcję IIF (cd, prawda, fałsz). Dlatego przy minimalnym wyborze możesz go używać jak

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

Podczas gdy IIF to tylko skrót do pisania CASE...WHEN...ELSE, łatwiej jest pisać.

Mert Gülsoy
źródło
8
IIFjest po prostu cukrem syntaktycznym CASE...WHEN...ELSE.
Salman,
55
Prawdopodobnie tak. Ale łatwiejsze do napisania.
Mert Gülsoy,
1
@ MertGülsoy I łatwiejszy do odczytania, który powinien znajdować się na szczycie listy priorytetów wszystkich, zaraz po poprawności.
Daniel
118

Rozwiązania wykorzystujące CASE, IIF i UDF są odpowiednie, ale niepraktyczne przy rozszerzaniu problemu na ogólny przypadek przy użyciu więcej niż 2 wartości porównawczych. Uogólnione rozwiązanie w SQL Server 2008+ wykorzystuje dziwne zastosowanie klauzuli VALUES:

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

Kredyt dzięki tej stronie: http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-maximum-value-from-some-columns-sql- server-t-sql.aspx

Craig
źródło
12
To najlepsza odpowiedź
FindOutIslamNow
jeśli chcesz min niezerowe:MIN(x*(case x when 0 then null else 1 end))
MPag
Tyle że MartinC udzielił tej samej odpowiedzi cztery lata wcześniej i faktycznie pokazał ją z więcej niż dwiema wartościami ...
Auspex
4
Auspex, odpowiedź MartinC jest niezwiązana. Ta odpowiedź nie używa związków.
Craig
30

Właśnie miałem sytuację, w której musiałem znaleźć maksymalnie 4 złożone selekcje w ramach aktualizacji. Dzięki takiemu podejściu możesz mieć tyle, ile chcesz!

Możesz także zastąpić liczby dodatkowymi zaznaczeniami

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

Bardziej złożone użycie

 @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

Jestem pewien, że UDF ma lepszą wydajność.

MartinC
źródło
Najbardziej podoba mi się ten, ponieważ jest to podstawowy SQL. Co więcej, UDF nie są koniecznie szybsze. W przypadku większości sklepów z kolumnami każdy atrybut (zakładam, że również będziesz filtrować według atrybutów) może być obliczany równolegle, a tylko zestaw kwalifikujący jest łączony. Związki nie są więc same w sobie wolne.
Wykidajło
proste i niesamowite.
ashleedawg
22

W przypadku MySQL lub PostgreSQL 9.3+ lepszym sposobem jest użycie funkcji LEASTi 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

Z:

  • GREATEST(value [, ...]): Zwraca największy (o maksymalnej wartości) argument z podanych wartości
  • LEAST(value [, ...])Zwraca najmniejszy (o minimalnej wartości) argument z podanych wartości

Linki do dokumentacji:

Gil Margolin
źródło
Działa to również w PostgreSQL (i dokładnie tego szukałem :) Zobacz: postgresql.org/docs/9.5/static/functions-conditional.html
Albert Vaca Cintora
1
Jak dotąd najlepsza odpowiedź.
Roberto Rodriguez
2
@RobertoRodriguez byłoby najlepiej, gdyby pytanie zawierało znaczniki MySQL lub PostgreSQL jako część pytania. Pytanie dotyczyło konkretnie tsql, więc ta odpowiedź w ogóle nie pomaga.
Jmaurier
to nie jest odpowiedź dla MSSQL
Mujah Maskey
13

Oto sztuczka, jeśli chcesz obliczyć maksimum (pole, 0):

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

zwraca 0, jeśli fieldjest ujemne, w przeciwnym razie return field.

mathematix
źródło
3
Aby obliczyć minimum (@a, @b), możesz użyć:SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2
scottyc
1
i nie zapomnij o przepełnieniu typu;)
pkuderov
Czy to oszczędza z zmiennoprzecinkowego punktu widzenia precyzji? Czy to pewne, że wynik nigdy nie będzie bliski zeru, ale ujemny?
zuraff
6

Użyj instrukcji CASE.

Przykład B na tej stronie powinien być zbliżony do tego, co próbujesz zrobić:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Oto kod ze strony:

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
źródło
2

Użyj tabeli temp, aby wstawić zakres wartości, a następnie wybierz min / max tabeli temp z procedury składowanej lub UDF. Jest to podstawowa konstrukcja, więc w razie potrzeby możesz ją zmienić.

Na przykład:

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
użytkownik1970604
źródło
2

Działa to maksymalnie dla 5 dat i obsługuje wartości zerowe. Po prostu nie mogłem go uruchomić jako funkcji Inline.

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
źródło
Właśnie zdałem sobie sprawę, że nie potrzebujesz klauzul WHERE, ponieważ MIN i tak usunie wartości Null.
Lawrence
2

Opierając się na genialnej logice / kodzie Mathematix i Scott, przesyłam:

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

Chociaż przejście z funkcji MIN szkockiej do funkcji MAX powinno być dla mnie oczywiste, nie było, więc rozwiązałem ją i umieściłem tutaj: SELECT @a + (ABS (@ b- @ a) + ( @ b- @ a)) / 2. Losowo generowane liczby, choć nie są dowodami, powinny przynajmniej przekonać sceptyków, że obie formuły są poprawne.

DaveX
źródło