Учитывая следующую таблицу в 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
ОБНОВИТЬ:
Для пояснения (как я уже сказал в комментариях) в реальном сценарии база данных должным образом нормализована . Эти столбцы «массива» находятся не в реальной таблице, а в результирующем наборе, который требуется в отчете. И новое требование состоит в том, что отчету также нужен столбец 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), это довольно элегантный вариант «выйти из тюрьмы». карта".
источник
Ответы:
Вероятно, есть много способов добиться этого. Я предлагаю использовать 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
источник
Использование
CROSS APPLY
:SELECT ID, Col1, Col2, Col3, MinValue FROM YourTable CROSS APPLY (SELECT MIN(d) AS MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A
SQL Fiddle
источник
cross apply
. Добавляет ли это ценность / производительность? stackoverflow.com/a/14712024/78522where MinValue > 10
, без чего я не смог бы обойтисьCROSS APPLY
SELECT ID, Col1, Col2, Col3, (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin FROM Table
источник
В MySQL используйте это:
select least(col1, col2, col3) FROM yourtable
источник
LEAST
работает с последней версией управляемых экземпляров Microsoft SQL Server ~ 12 дней назад. reddit.com/r/SQLServer/comments/k0dj2r/…Вы можете использовать подход «грубой силы» с изюминкой:
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
оператора как можно раньше.источник
<=
вместо этого, иначе будет использоваться последнее совпадающее минимальное значение вместо первого.Если бы столбцы были целыми числами, как в вашем примере, я бы создал функцию:
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)
источник
Лучший способ сделать это, вероятно, - это не делать этого - странно, что люди настаивают на хранении своих данных таким образом, который требует "гимнастики" 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
время, обычно является плохой идеей с точки зрения производительности, поскольку данные изменяются только при вставке / обновлении - добавление другого столбца занимает больше места в БД и будет немного медленнее для вставок и обновляется, но может быть намного быстрее для выборок - предпочтительный подход должен зависеть от ваших приоритетов, но, как указано, большинство таблиц читаются гораздо чаще, чем пишутся.источник
Вы также можете сделать это с помощью запроса на объединение. По мере увеличения количества столбцов вам нужно будет изменить запрос, но, по крайней мере, это будет прямая модификация.
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
источник
Это грубая сила, но работает
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 () работает только с одним столбцом, а не по столбцам.
источник
И на этот вопрос, и на этот вопрос попытайтесь ответить на этот вопрос.
Напомним, что в Oracle есть встроенная функция для этого, с Sql Server вы застряли либо при определении определяемой пользователем функции, либо при использовании операторов case.
источник
Для нескольких столбцов лучше всего использовать оператор CASE, однако для двух числовых столбцов i и j вы можете использовать простую математику:
min (i, j) = (i + j) / 2 - абс (ij) / 2
Эту формулу можно использовать для получения минимального значения нескольких столбцов, но это действительно беспорядочное прошлое 2, min (i, j, k) будет min (i, min (j, k))
источник
Если вы можете создать хранимую процедуру, она может принимать массив значений, и вы можете просто вызвать это.
источник
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
источник
Небольшой поворот в запросе на объединение:
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
источник
Если вы используете 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
Так в таком количестве операторов не потеряешься :)
Однако этот вариант может быть медленнее, чем другой вариант.
Это твой выбор...
источник
Ниже я использую временную таблицу, чтобы получить как минимум несколько дат. Первая временная таблица запрашивает несколько объединенных таблиц для получения различных дат (а также других значений для запроса), вторая временная таблица затем получает различные столбцы и минимальную дату, используя столько проходов, сколько имеется столбцов даты.
По сути, это похоже на запрос на объединение, требуется такое же количество проходов, но он может быть более эффективным (исходя из опыта, но потребуется тестирование). В этом случае эффективность не была проблемой (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
источник
SELECT [ID], ( SELECT MIN([value].[MinValue]) FROM ( VALUES ([Col1]), ([Col1]), ([Col2]), ([Col3]) ) AS [value] ([MinValue]) ) AS [MinValue] FROM Table;
источник
Если вы знаете, какие значения ищете, обычно это код состояния, может быть полезно следующее:
select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS, PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end FROM CUSTOMERS_FORMS
источник
Я знаю, что этот вопрос старый, но я все еще нуждался в ответе и был недоволен другими ответами, поэтому мне пришлось придумать свой собственный, который является поворотом к ответу @paxdiablo .
Я пришел из страны SAP ASE 16.0, и мне нужно было только взглянуть на статистику определенных данных, которые, IMHO, корректно хранятся в разных столбцах одной строки (они представляют разное время - когда прибытие чего-то планировалось, что ожидалось, когда действие началось и, наконец, сколько было фактического времени). Таким образом, я перенес столбцы в строки временной таблицы и, как обычно, выполнил свой запрос.
NB Впереди не универсальное решение!
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, ...) должны были помочь мне в поиске, поэтому они могут вам не понадобиться; Я оставил их здесь, чтобы, возможно, зажечь какие-нибудь идеи :-).источник