从几列中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;