2008-12-15 116 views
45

鉴于在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列的所有查询要求),这是一个非常优雅的“走出监狱卡”。

+10

恕我直言,作者的UNPIVOT解决方案优于其他答案。 – 2011-11-11 11:31:45

+1

我发现Nizam的解决方案是最精简的解决方案,即使我花了一段时间才开始了解它。精益和非常有用。 – 2015-08-27 15:09:18

回答

34

有可能有很多方法来实现这一点。我的建议是使用案例/何时去做。有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 
+3

这是我最初的想法。但真正的查询需要5列,列数可能会增加。所以CASE方法变得有点笨拙。但它确实有效。 – stucampbell 2008-12-15 13:41:07

+1

如果列数可能增长,那么您肯定会做错了 - 请参阅我的文章(关于为什么您不应该使用这种方式设置数据库模式:-)。 – paxdiablo 2008-12-15 13:49:15

+0

谢谢。正如我在另一评论中提到的。我没有查询实际的表格。表格已正确归一化。该查询是特别复杂的查询的一部分,并且正在处理派生表的中间结果。 – stucampbell 2008-12-15 13:57:17

2

这是蛮力,但工程

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()仅适用于一列,而不是跨列。

7

最好的办法其实是做到这一点 - 这是奇怪的是,人们坚持存储需要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 

对待个人“老列”。如果“老专栏”的数量增加,这也容易扩展。

这使得你的查询所以容易得多。一般准则我倾向于使用,如果你曾经东西,看起来像在一个数据库行的一个数组,你可能做错事,应该考虑重组数据。


但是,如果由于某种原因你不能改变这些列,我建议使用INSERT和UPDATE触发器,并添加另一列这些触发器设置为最小的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时间做出决定通常是一个坏主意性能明智的,因为数据只在插入/更新变化 - 加入另一列占用更多的空间,在DB和将是插入和更新速度较慢,但​​可以更快的选择 - 首选的方法应该取决于你的优先次序出现,但正如指出,大多数表是只读比他们写的更多。

+9

嗯。感谢谩骂。真正的数据库已正确归一化。这是一个简单的例子。实际查询很复杂,我感兴趣的5列是派生表的中间结果。 – stucampbell 2008-12-15 13:53:26

+3

谩骂仍然不幸。制作你所建议的表格的中间表格与制作永久表格一样有问题。事实证明这一点,您必须执行我喜欢称为SQL体操的操作才能获得您想要的结果。 – paxdiablo 2008-12-15 14:04:41

+0

如果在单行中需要“数组”的真正原因,请随时启发我们,但使用它来选择最小值不是其中之一。 – paxdiablo 2008-12-15 14:05:49

1

如果你能做出一个存储过程,可能需要值的数组,你可以只调用。

1
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 
5

你也可以用联合查询来做到这一点。随着列数的增加,您需要修改查询,但至少它是一个简单的修改。

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 
1

如果使用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 

这样你就不会迷失在这么多的运营商:)

然而,这可能是比其他选择慢。

这是你的选择......

2

两个this questionthis question试图回答这个问题。

回顾一下,甲骨文有一个内置的函数,在Sql Server中,你定义了一个用户自定义函数或使用case语句。

1

联合查询有点扭曲:

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 
6

如果列是整数,如你的例子我会创造一个功能:

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个colums那么上面变得

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) 
0

如果你知道什么值你所寻找的,通常是一个状态代码,下面可以帮助:

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

使用此:

select least(col1, col2, col3) FROM yourtable 
7

您可以使用“蛮力力”与一捻方法:

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声明。

24

使用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

1

下面我用一个临时表,以获得最低的几个日期。第一个临时表查询几个连接的表以获得各种日期(以及查询的其他值),第二个临时表然后使用与日期列一样多的传递来获取各个列和最小日期。

这实质上就像联合查询一样,需要相同数量的通行证,但可能更高效(基于经验,但需要测试)。在这种情况下效率不是问题(8,000条记录)。可以索引等。

--==================== 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 
4
SELECT ID, Col1, Col2, Col3, 
    (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin 
FROM Table 
0

对于使用CASE语句多列其最好的,但是有两个数字列i和j可以用简单的数学:

分钟(I,J)=(I + J )/ 2 - abs(ij)/ 2

这个公式可以用来得到多列的最小值,但是它的真正凌乱的过去2,min(i,j,k)是min(i,min( j,k))

0
SELECT [ID], 
      (
       SELECT MIN([value].[MinValue]) 
       FROM 
       (
        VALUES 
         ([Col1]), 
         ([Col1]), 
         ([Col2]), 
         ([Col3]) 
       ) AS [value] ([MinValue]) 
      ) AS [MinValue] 
FROM Table; 
相关问题