2008-12-15 113 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; 
相關問題