Каков наилучший способ выбрать минимальное значение из нескольких столбцов? - PullRequest
62 голосов
/ 15 декабря 2008

С учетом следующей таблицы в SQL Server 2005:

ID   Col1   Col2   Col3
--   ----   ----   ----
1       3     34     76  
2      32    976     24
3       7    235      3
4     245      1    792

Как лучше всего написать запрос, который дает следующий результат (т. Е. Тот, который возвращает последний столбец - столбец, содержащий минимальные значения из Col1, Col2 и Col 3 для каждой строки )

ID   Col1   Col2   Col3  TheMin
--   ----   ----   ----  ------
1       3     34     76       3
2      32    976     24      24
3       7    235      3       3
4     245      1    792       1

UPDATE:

Для пояснения (как я уже сказал в комментариях) в реальном сценарии база данных правильно нормализована . Эти столбцы «массива» находятся не в реальной таблице, а в наборе результатов, который требуется в отчете. И новым требованием является то, что отчет также нуждается в этом столбце MinValue. Я не могу изменить базовый набор результатов, и поэтому я искал T-SQL для удобной карты «выйти из тюрьмы».

Я попробовал подход CASE, упомянутый ниже, и он работает, хотя он немного громоздок. Это также сложнее, чем указано в ответах, потому что вам нужно учитывать тот факт, что в одной строке есть два минимальных значения.

В любом случае, я думал, что опубликую свое текущее решение, которое, учитывая мои ограничения, работает довольно хорошо. Используется оператор UNPIVOT:

with cte (ID, Col1, Col2, Col3)
as
(
    select ID, Col1, Col2, Col3
    from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
    select
        ID, min(Amount) as TheMin
    from 
        cte 
        UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
    group by ID
) as minValues
on cte.ID = minValues.ID

Скажу сразу, что не ожидаю, что это даст лучшую производительность, но, учитывая обстоятельства (я не могу перепроектировать все запросы только для нового требования к столбцу MinValue), это довольно элегантно. вне тюрьмы ".

Ответы [ 19 ]

45 голосов
/ 15 декабря 2008

Вероятно, есть много способов сделать это. Я предлагаю использовать Case / When, чтобы сделать это. С 3 колонками это не так уж плохо.

Select Id,
       Case When Col1 < Col2 And Col1 < Col3 Then Col1
            When Col2 < Col1 And Col2 < Col3 Then Col2 
            Else Col3
            End As TheMin
From   YourTableNameHere
41 голосов
/ 24 апреля 2015

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

SELECT ID, Col1, Col2, Col3, MinValue
FROM YourTable
CROSS APPLY (SELECT MIN(d) MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A

SQL Fiddle

9 голосов
/ 04 января 2016
SELECT ID, Col1, Col2, Col3, 
    (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table
7 голосов
/ 15 декабря 2008

Лучший способ сделать это, вероятно, , а не , чтобы сделать это - странно, что люди настаивают на хранении своих данных способом, который требует "гимнастики" SQL для извлечения значимой информации, когда гораздо проще способы достижения желаемого результата, если вы просто лучше структурируете свою схему: -)

правильный способ сделать это, на мой взгляд, это иметь следующую таблицу:

ID    Col    Val
--    ---    ---
 1      1      3
 1      2     34
 1      3     76

 2      1     32
 2      2    976
 2      3     24

 3      1      7
 3      2    235
 3      3      3

 4      1    245
 4      2      1
 4      3    792

с ID/Col в качестве первичного ключа (и, возможно, Col в качестве дополнительного ключа, в зависимости от ваших потребностей). Тогда ваш запрос становится простым select min(val) from tbl, и вы все равно можете обрабатывать отдельные «старые столбцы» отдельно, используя where col = 2 в других ваших запросах. Это также позволяет легко расширяться при увеличении количества «старых столбцов».

Это упрощает ваши запросы , поэтому . Общее правило, которое я обычно использую: если у вас когда-либо есть что-то, похожее на массив в строке базы данных, вы, вероятно, делаете что-то не так и должны подумать о реструктуризации данных.


Однако, если по какой-то причине вы не можете изменить эти столбцы, я бы предложил использовать триггеры вставки и обновления и добавить еще один столбец , для которого эти триггеры установлены на минимум. Col1/2/3. Это переместит «стоимость» операции с выбора на обновление / вставку, к которому она относится - большинство таблиц базы данных в моем опыте читаются гораздо чаще, чем записываются, поэтому затраты на запись, как правило, со временем становятся более эффективными.

Другими словами, минимум для строки изменяется только при изменении одного из других столбцов, поэтому это , когда вам нужно его вычислять, а не каждый раз, когда вы выбираете (что теряется, если данные не не меняется). После этого вы получите таблицу типа:

ID   Col1   Col2   Col3   MinVal
--   ----   ----   ----   ------
 1      3     34     76        3
 2     32    976     24       24
 3      7    235      3        3
 4    245      1    792        1

Любой другой вариант, который должен принимать решения во время select, обычно является плохой идеей с точки зрения производительности, поскольку данные изменяются только при вставке / обновлении - добавление еще одного столбца занимает больше места в БД и будет немного медленнее для вставок и обновлений, но может быть намного быстрее для выбора - предпочтительный подход должен зависеть от ваших приоритетов, но, как указано, большинство таблиц читаются далеко чаще, чем они написано.

7 голосов
/ 02 марта 2015

Вы можете использовать подход "грубой силы" с изюминкой:

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1
    WHEN                  Col2 <= Col3 THEN Col2
    ELSE                                    Col3
END AS [Min Value] FROM [Your Table]

Когда первое условие не выполняется, это гарантирует, что Col1 не наименьшее значение, поэтому вы можете исключить его из остальных условий. Аналогично для последующих условий. Для пяти столбцов ваш запрос становится:

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
    WHEN                  Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
    WHEN                                   Col3 <= Col4 AND Col3 <= Col5 THEN Col3
    WHEN                                                    Col4 <= Col5 THEN Col4
    ELSE                                                                      Col5
END AS [Min Value] FROM [Your Table]

Обратите внимание, что при наличии связи между двумя или более столбцами <= гарантирует, что мы выйдем из оператора CASE как можно раньше.

6 голосов
/ 28 августа 2012

Если бы столбцы были целыми числами, как в вашем примере, я бы создал функцию:

create function f_min_int(@a as int, @b as int) 
returns int
as
begin
    return case when @a < @b then @a else coalesce(@b,@a) end
end

тогда, когда мне нужно его использовать, я бы сделал:

select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)

если у вас 5 столбцов, то выше становится

select col1, col2, col3, col4, col5,
dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(col1,col2),col3),col4),col5)
5 голосов
/ 03 апреля 2014

Используйте это:

select least(col1, col2, col3) FROM yourtable
5 голосов
/ 15 декабря 2008

Вы также можете сделать это с помощью запроса объединения. По мере увеличения числа столбцов вам нужно будет изменить запрос, но по крайней мере это будет прямое изменение.

Select T.Id, T.Col1, T.Col2, T.Col3, A.TheMin
From   YourTable T
       Inner Join (
         Select A.Id, Min(A.Col1) As TheMin
         From   (
                Select Id, Col1
                From   YourTable

                Union All

                Select Id, Col2
                From   YourTable

                Union All

                Select Id, Col3
                From   YourTable
                ) As A
         Group By A.Id
       ) As A
       On T.Id = A.Id
3 голосов
/ 15 декабря 2008

Это грубая сила, но работает

 select case when col1 <= col2 and col1 <= col3 then col1
           case when col2 <= col1 and col2 <= col3 then col2
           case when col3 <= col1 and col3 <= col2 then col3
    as 'TheMin'
           end

from Table T

... потому что min () работает только для одного столбца, а не для всех столбцов.

2 голосов
/ 16 декабря 2008

Оба этот вопрос И на этот вопрос попробуйте ответить на этот вопрос.

Напомним, что Oracle имеет встроенную функцию для этого, с Sql Server вы застряли либо в определении пользовательской функции, либо в использовании операторов case.

...