Получение как минимум двух значений в SQL

180

У меня есть две переменные, одна называется PaidThisMonth, а другая называется OwedPast. Оба они являются результатами некоторых подзапросов в SQL. Как я могу выбрать меньшее из двух и вернуть его в качестве значения с названием PaidForPast?

MINФункция работает по столбцам, а не переменных.

Malfist
источник
1
Если вы используете Postgres или MySQL, перейдите к ответу @ Gil_Margolin.
Нумен

Ответы:

127

Случай использования:

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

Как Inline таблица ценится UDF

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

Использование:

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

ADDENDUM: Это, вероятно, лучше всего для адресации только двух возможных значений, если их больше двух, рассмотрите ответ Крейга, используя предложение Values.

Чарльз Бретана
источник
лучше понятный синтаксис: return (выберите minValue = случай, когда @@ param1 <@@ param2 затем @@ param1 else @@ param2 end). Хорошо, это не может быть нормализовано, я не знаю. Но это гораздо понятнее и должно быть нормализовано.
Softlion
1
Другая причина предпочесть ответ @ Крейга ниже - из-за нулевой обработки. Если сравниваемые значения имеют значение NULL, а одно из сравниваемых значений имеет значение NULL, показанный случай переключения может возвращать NULL или значение в зависимости от порядка выполнения теста WHEN (если только вы не добавите использование ISNULL). Подход Крейга всегда будет отдавать предпочтение выбору ненулевого значения, которое мне кажется более правильным, по крайней мере, в моем текущем сценарии использования при сравнении значений, допускающих обнуление.
Ния
148

SQL Server 2012 и 2014 поддерживает функцию IIF (продолжение, истина, ложь). Таким образом, для минимального выбора вы можете использовать его как

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

В то время как IIF - это просто сокращение для записи CASE...WHEN...ELSE, писать легче.

Мерт Гюльсой
источник
8
IIFэто просто синтаксический сахар для CASE...WHEN...ELSE.
Салман А
55
Возможно, да. Но проще написать.
Мерт Гюльсой
1
@ MertGülsoy И легче читать, который должен быть в верхней части списка приоритетов каждого, сразу после правильности.
Даниил
119

Решения, использующие CASE, IIF и UDF, являются адекватными, но нецелесообразными при распространении задачи на общий случай с использованием более двух значений сравнения. Обобщенное решение в SQL Server 2008+ использует странное приложение предложения VALUES:

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

Кредит за этот сайт: http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-maximum-value-from-some-columns-sql- сервер-трет-sql.aspx

Craig
источник
12
Это лучший ответ
FindOutIslamNow
если вы хотите мин ненулевой:MIN(x*(case x when 0 then null else 1 end))
mpag
За исключением того, что MartinC дал тот же ответ четырьмя годами ранее, и фактически показал его с более чем двумя значениями ...
Auspex
4
Auspex, ответ MartinC не имеет отношения. Этот ответ не использует союзы.
Крейг
30

У меня просто была ситуация, когда я должен был найти максимум 4 сложных выбора в обновлении. При таком подходе вы можете иметь столько, сколько захотите!

Вы также можете заменить номера дополнительным выбором

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

Более сложное использование

 @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

Я уверен, что UDF имеет лучшую производительность.

MartinC
источник
Мне это нравится больше всего, так как это базовый SQL. Кроме того, UDF не обязательно намного быстрее. Для большинства хранилищ столбцов каждый атрибут (я предполагаю, что вы также собираетесь фильтровать атрибуты) может быть вычислен параллельно, и объединяется только соответствующий набор. Так что союзы не медленны сами по себе.
Bouncner
просто и круто
ashleedawg
22

Для MySQL или PostgreSQL 9.3+, лучший способ заключается в использовании LEASTи 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

С участием:

  • GREATEST(value [, ...])Возвращает самый большой (максимальный) аргумент из предоставленных значений.
  • LEAST(value [, ...])Возвращает наименьший (минимальный) аргумент из предоставленных значений

Ссылки на документацию:

Гил Марголин
источник
Это также работает в PostgreSQL (и это именно то, что я искал :) См .: postgresql.org/docs/9.5/static/functions-conditional.html
Альберт Вака Синтора
1
Это лучший ответ на сегодняшний день.
Роберто Родригес
2
@RobertoRodriguez было бы лучше, если бы вопрос был помечен MySQL или PostgreSQL как часть вопроса. Вопрос был конкретно о tsql, так что этот ответ не помогает вообще.
Jmaurier
это не ответ на MSSQL
Муджа Маски
13

Вот хитрость, если вы хотите вычислить максимум (поле, 0):

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

возвращает 0, если fieldотрицателен, иначе вернуть field.

mathematix
источник
3
Итак, чтобы вычислить минимум (@a, @b), вы можете использовать:SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2
scottyc
1
и не забывайте про переполнение типа;)
пкудеров
Сохраняется ли это с точки зрения точности с плавающей точкой? Уверен ли, что результат никогда не будет близок к нулю, а будет отрицательным?
Зураф
6

Используйте оператор CASE.

Пример B на этой странице должен быть близок к тому, что вы пытаетесь сделать:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Вот код со страницы:

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
Майк Коул
источник
2

Используйте временную таблицу для вставки диапазона значений, затем выберите минимальную / максимальную временную таблицу из хранимой процедуры или пользовательской функции. Это базовая конструкция, поэтому не стесняйтесь пересматривать по мере необходимости.

Например:

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
источник
2

Это работает до 5 дат и обрабатывает нули. Просто не мог заставить его работать как встроенная функция.

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
Лоренс
источник
Просто понял, что вам не нужны предложения WHERE, поскольку MIN все равно удалит Nulls.
Лоуренс
2

Основываясь на блестящей логике / коде от Mathematix и Scottyc, я представляю:

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

Хотя переход с функции scottyc на функцию MIN к функции MAX должен был быть для меня очевиден, этого не произошло, поэтому я решил это и включил здесь: SELECT @a + (ABS (@ b- @ a) + ( @ b- @ a)) / 2. Случайно сгенерированные числа, хотя и не являются доказательством, должны, по крайней мере, убедить скептиков в правильности обеих формул.

Davex
источник