Каков наилучший способ выбрать минимальное значение из нескольких столбцов? - 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 ]

1 голос
/ 10 ноября 2017
SELECT [ID],
            (
                SELECT MIN([value].[MinValue])
                FROM
                (
                    VALUES
                        ([Col1]),
                        ([Col1]),
                        ([Col2]),
                        ([Col3])
                ) AS [value] ([MinValue])
           ) AS [MinValue]
FROM Table;
1 голос
/ 11 октября 2016

Для нескольких столбцов лучше всего использовать оператор CASE, однако для двух числовых столбцов i и j можно использовать простую математику:

мин (i, j) = (i + j) / 2 - абс (i-j) / 2

Эту формулу можно использовать для получения минимального значения нескольких столбцов, но за последние 2 запутанные значения min (i, j, k) будут min (i, min (j, k))

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

Если вы можете создать хранимую процедуру, она может принять массив значений, и вы можете просто вызвать это.

1 голос
/ 02 января 2016

Ниже я использую временную таблицу, чтобы получить минимум несколько дат. Первая временная таблица запрашивает несколько объединенных таблиц, чтобы получить различные даты (а также другие значения для запроса), вторая временная таблица затем получает различные столбцы и минимальную дату, используя столько раз, сколько имеется столбцов даты.

Это, по сути, похоже на запрос объединения, требуется такое же количество проходов, но оно может быть более эффективным (исходя из опыта, но потребует тестирования). Эффективность не была проблемой в этом случае (8000 записей). Можно индексировать и т. Д.

--==================== this gets minimums and global min
if object_id('tempdb..#temp1') is not null
    drop table #temp1
if object_id('tempdb..#temp2') is not null
    drop table #temp2

select r.recordid ,  r.ReferenceNumber, i.InventionTitle, RecordDate, i.ReceivedDate
, min(fi.uploaddate) [Min File Upload], min(fi.CorrespondenceDate) [Min File Correspondence]
into #temp1
from record r 
join Invention i on i.inventionid = r.recordid
left join LnkRecordFile lrf on lrf.recordid = r.recordid
left join fileinformation fi on fi.fileid = lrf.fileid
where r.recorddate > '2015-05-26'
 group by  r.recordid, recorddate, i.ReceivedDate,
 r.ReferenceNumber, i.InventionTitle



select recordid, recorddate [min date]
into #temp2
from #temp1

update #temp2
set [min date] = ReceivedDate 
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.ReceivedDate < [min date] and  t1.ReceivedDate > '2001-01-01'

update #temp2 
set [min date] = t1.[Min File Upload]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Upload] < [min date] and  t1.[Min File Upload] > '2001-01-01'

update #temp2
set [min date] = t1.[Min File Correspondence]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Correspondence] < [min date] and t1.[Min File Correspondence] > '2001-01-01'


select t1.*, t2.[min date] [LOWEST DATE]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
order by t1.recordid
1 голос
/ 15 декабря 2008
select *,
case when column1 < columnl2 And column1 < column3 then column1
when columnl2 < column1 And columnl2 < column3 then columnl2
else column3
end As minValue
from   tbl_example
1 голос
/ 31 августа 2011

Небольшой поворот в запросе объединения:

DECLARE @Foo TABLE (ID INT, Col1 INT, Col2 INT, Col3 INT)

INSERT @Foo (ID, Col1, Col2, Col3)
VALUES
(1, 3, 34, 76),
(2, 32, 976, 24),
(3, 7, 235, 3),
(4, 245, 1, 792)

SELECT
    ID,
    Col1,
    Col2,
    Col3,
    (
        SELECT MIN(T.Col)
        FROM
        (
            SELECT Foo.Col1 AS Col UNION ALL
            SELECT Foo.Col2 AS Col UNION ALL
            SELECT Foo.Col3 AS Col 
        ) AS T
    ) AS TheMin
FROM
    @Foo AS Foo
1 голос
/ 15 декабря 2008

Если вы используете SQL 2005, вы можете сделать что-то вроде этого:

;WITH    res
          AS ( SELECT   t.YourID ,
                        CAST(( SELECT   Col1 AS c01 ,
                                        Col2 AS c02 ,
                                        Col3 AS c03 ,
                                        Col4 AS c04 ,
                                        Col5 AS c05
                               FROM     YourTable AS cols
                               WHERE    YourID = t.YourID
                             FOR
                               XML AUTO ,
                                   ELEMENTS
                             ) AS XML) AS colslist
               FROM     YourTable AS t
             )
    SELECT  YourID ,
            colslist.query('for $c in //cols return min(data($c/*))').value('.',
                                            'real') AS YourMin ,
            colslist.query('for $c in //cols return avg(data($c/*))').value('.',
                                            'real') AS YourAvg ,
            colslist.query('for $c in //cols return max(data($c/*))').value('.',
                                            'real') AS YourMax
    FROM    res

Таким образом, вы не заблудитесь в таком количестве операторов:)

Однако это может быть медленнее, чем другой выбор.

Это твой выбор ...

0 голосов
/ 18 апреля 2019

Я знаю, что этот вопрос старый, но я все еще нуждался в ответе и не был доволен другими ответами, поэтому мне пришлось придумать свой, который искажает @ paxdiablo´s answer .


Я приехал из страны SAP ASE 16.0, и мне нужно было только посмотреть статистику определенных данных, которые ИМХО достоверно хранятся в разных столбцах одной строки (они представляют разное время - когда планировалось прибытие чего-либо, что это ожидалось, когда действие началось и, наконец, сколько времени было в действительности). Таким образом, я переместил столбцы в строки временной таблицы и подготовил свой запрос, как обычно.

N.B. Не универсальное решение для всех!

CREATE TABLE #tempTable (ID int, columnName varchar(20), dataValue int)

INSERT INTO #tempTable 
  SELECT ID, 'Col1', Col1
    FROM sourceTable
   WHERE Col1 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col2', Col2
    FROM sourceTable
   WHERE Col2 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col3', Col3
    FROM sourceTable
   WHERE Col3 IS NOT NULL

SELECT ID
     , min(dataValue) AS 'Min'
     , max(dataValue) AS 'Max'
     , max(dataValue) - min(dataValue) AS 'Diff' 
  FROM #tempTable 
  GROUP BY ID

Это заняло около 30 секунд для исходного набора из 630000 строк и использовало только индексные данные, поэтому не для запуска в критичном по времени процессе, а для таких вещей, как одноразовая проверка данных или отчет на конец дня С вами может быть все в порядке (но уточните это у ваших сверстников или начальников, пожалуйста!). Главный бонус этого стиля для меня состоял в том, что я мог легко использовать больше / меньше столбцов и изменять группировку, фильтрацию и т. Д., Особенно после копирования данных.

Дополнительные данные (columnName, max es, ...) должны были помочь мне в моем поиске, поэтому они могут вам не понадобиться; Я оставил их здесь, чтобы, возможно, зажечь некоторые идеи: -).

0 голосов
/ 28 августа 2013

Если вы знаете, какие значения вы ищете, обычно это код состояния, вам может помочь следующее:

select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS
...