从几列中select最小值的最佳方法是什么?
鉴于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方法,虽然它有点麻烦,但它工作正常。 这也比在答案中陈述的要复杂得多,因为你需要迎合在同一行中有两个最小值的事实。
无论如何,我想我会发布我目前的解决scheme,由于我的限制,工作得很好。 它使用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
我会提前说,我不希望这样做能提供最好的性能,但考虑到这种情况(我不能重新devise所有的查询,只是针对新的MinValue列要求),这是一个非常优雅的“走出监狱卡”。
有可能有很多方法来实现这一点。 我的build议是使用案例/何时去做。 有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) MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A
SQL小提琴
最好的方法实际上不这样做 – 奇怪的是,人们坚持以一种需要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
在你的其他查询。 如果“老专栏”的数量增加,这也容易扩张。
这使您的查询变得如此简单。 我倾向于使用的一般原则是,如果你在数据库行中看起来像一个数组,那么你可能做错了,应该考虑重构数据。
但是,如果由于某种原因,你不能改变这些列,我build议使用插入和更新触发器,并添加另一列,这些触发器设置为最小的Col1/2/3
。 这会将操作的“成本”从select移动到更新/插入所在的位置 – 根据我的经验,大多数数据库表读取的次数远远多于写入,所以随着时间的推移写入成本趋于更高效。
换句话说,行的最小值只有在其他列变化时才会改变, 这就是当你计算它时,并不是每次你select(如果数据没有改变,这是浪费的)。 然后你会得到一个像这样的表格:
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 – 首选的方法应该取决于你的优先顺序,但如前所述,大多数表读取的次数多于写入的次数。
如果列是像你的例子中的整数,我会创build一个函数:
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)
你可以使用“蛮力”方法:
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
语句。
你也可以用联合查询来做到这一点。 随着列数的增加,您需要修改查询,但至less这将是一个简单的修改。
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 ID, Col1, Col2, Col3, (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin FROM Table
这是蛮力,但工程
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语句。
如果你能够做一个存储过程,它可能需要一个值的数组,你可以调用它。
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
这样你就不会迷失在这么多的运营商:)
但是,这可能比其他select慢。
这是你的select…
下面我使用临时表来获取几个date的最小值。 第一个临时表查询几个连接的表以获取各种date(以及查询的其他值),第二个临时表然后使用与date列一样多的通路获取各个列和最小date。
这基本上就像联合查询,需要相同数量的通行证,但可能更有效率(根据经验,但需要testing)。 在这种情况下效率不是问题(8000个logging)。 可以索引等
--==================== 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 case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS, PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end FROM CUSTOMERS_FORMS
用这个:
select least(col1, col2, col3) FROM yourtable
对于多列,最好使用CASE语句,但是对于两个数字列i和j,可以使用简单的math运算:
min(i,j)=(i + j)/ 2-abs(ij)/ 2
这个公式可以用来得到多列的最小值,但是它的真正凌乱的过去2,min(i,j,k)将是min(i,min(j,k))
SELECT [ID], ( SELECT MIN([value].[MinValue]) FROM ( VALUES ([Col1]), ([Col1]), ([Col2]), ([Col3]) ) AS [value] ([MinValue]) ) AS [MinValue] FROM Table;