2010-06-25 38 views
2

我有類似以下內容(簡化)表模式:如何有效計算一列中的MAX,並按另一列排序?

CREATE TABLE Transactions 
(
    TransactionID int NOT NULL IDENTITY(1, 1) PRIMARY KEY CLUSTERED, 
    CustomerID int NOT NULL, -- Foreign key, not shown 
    TransactionDate datetime NOT NULL, 
    ... 
) 

CREATE INDEX IX_Transactions_Customer_Date 
ON Transactions (CustomerID, TransactionDate) 

給這裏有點背景的,這個事務表實際上是從另一個供應商的數據庫整合幾種不同類型的交易(我們會打電話給它是一個ETL過程),因此我不能很好地控制它們插入的順序。即使我這樣做了,交易也可能會回來,所以這裏需要注意的是對於任何給定的customer的最大TransactionID不一定是最近的交易。

事實上,最近的交易是日期 ID的組合。日期不是唯一的 - 供應商通常會截斷一天中的時間 - 爲了獲得最近的交易,我必須先找到最近的日期,然後找到該日期的最新ID。

我知道我可以用窗口查詢(ROW_NUMBER() OVER (PARTITION BY TransactionDate DESC, TransactionID DESC))來做到這一點,但這需要完整的索引掃描和非常昂貴的排序,因此在效率方面失敗了。不停地寫作也很尷尬。

稍微更有效的是使用兩個CTE或嵌套子查詢,一個找到MAX(TransactionDate)CustomerID,另一個找到MAX(TransactionID)。同樣,它可以工作,但需要第二次聚合和加入,這比ROW_NUMBER()查詢稍好,但仍然相當痛苦的性能明智。

我也考慮過使用CLR用戶定義的聚合,如果有必要的話,將回退,但我更希望找到一個純粹的SQL解決方案,如果可能的話,以簡化部署(不需要SQL-CLR這個項目的其他地方)。

所以現在的問題,具體爲:

是否可以編寫一個查詢將返回最新TransactionIDCustomerID,定義爲最大TransactionID對於最近TransactionDate,並實現了計劃相當於在性能上向普通MAX/GROUP BY查詢?

(換句話說,在該計劃的唯一顯著的步驟應該是一個索引掃描和數據流聚集。多重掃描,排序,連接等可能是太慢了。)

回答

0

免責聲明:大聲思考了:)

您是否有一個將TransactionDate和TransactionID列組合到一個表單中的索引計算列,這意味着查找最新事務只是找到該單個字段的MAX的情況?

+0

即使這對實現細節有一點點亮,結合字段的*概念實際上是需要的想出一個優化的解決方案我討厭自我接受,我會給你支票。 ;) – Aaronaught 2010-07-09 14:10:29

1

最有用指數可能是:

CustomerID, TransactionDate desc, TransactionId desc 

那麼你可以嘗試這樣的查詢:

select a.CustomerID 
,  b.TransactionID 
from (
     select distinct 
       CustomerID 
     from YourTable 
     ) a 
cross apply 
     (
     select top 1 
       TransactionID 
     from YourTable 
     where CustomerID = a.CustomerID 
     order by 
       TransactionDate desc, 
       TransactionId desc 
     ) b 
+0

應該是'CROSS APPLY',否則它不會解析。這是我沒有想到的;我剛剛測試過它,它似乎與「ROW_NUMBER」解決方案大致相同。 :(沒有排序,但是除了完整掃描之外還會出現一個'索引搜索(Index Seek)'(我預計它會更快,但結果是掃描的3倍)。有一些我沒有考慮過的東西 – Aaronaught 2010-06-25 14:52:39

+0

@Aaronaught:編輯過,也許DESC索引稍微快一點,注意這個解決方案沒有索引就沒什麼意義 – Andomar 2010-06-25 14:57:24

+0

實際上,索引與一個索引沒有太大的區別我有 - 每個CustomerID,TransactionDate對有相對較少的(但不止一個)事務,'DESC'索引確實改進了事情(它也改進了ROW_NUMBER())查詢;但是,我寧願不要基本上覆制一個現有的索引,我相當確信有一種方法可以在一次掃描中完成這項工作。 – Aaronaught 2010-06-25 15:03:53

1

如何強制優化器首先計算派生表。在我的測試中,這比兩個Max比較便宜。

Select T.CustomerId, T.TransactionDate, Max(TransactionId) 
From Transactions As T 
    Join (
      Select T1.CustomerID, Max(T1.TransactionDate) As MaxDate 
      From Transactions As T1 
      Group By T1.CustomerId 
      ) As Z 
     On Z.CustomerId = T.CustomerId 
      And Z.MaxDate = T.TransactionDate 
Group By T.CustomerId, T.TransactionDate 
0

這一個似乎具有良好的性能統計數據:

SELECT 
    T1.customer_id, 
    MAX(T1.transaction_id) AS transaction_id 
FROM 
    dbo.Transactions T1 
INNER JOIN 
(
    SELECT 
     T2.customer_id, 
     MAX(T2.transaction_date) AS max_dt 
    FROM 
     dbo.Transactions T2 
    GROUP BY 
     T2.customer_id 
) SQ1 ON 
    SQ1.customer_id = T1.customer_id AND 
    T1.transaction_date = SQ1.max_dt 
GROUP BY 
    T1.customer_id 
0

我覺得其實我想通了。 @Ada有正確的想法,我自己也有同樣的想法,但堅持如何形成一個複合ID並避免額外的連接。

由於日期和(正)整數都是按字節排序的,因此它們不僅可以連接成BLOB進行聚合,而且在聚合完成後也可以分開。

這感覺有點邪惡,但似乎這樣的伎倆:

SELECT 
    CustomerID, 
    CAST(SUBSTRING(MAX(
     CAST(TransactionDate AS binary(8)) + 
     CAST(TransactionID AS binary(4))), 
     9, 4) AS int) AS TransactionID 
FROM Transactions 
GROUP BY CustomerID 

這給了我一個索引掃描和流聚集。不需要額外的索引,它的執行效果與MAX(TransactionID)一樣 - 顯然這很有意義,因爲所有的連接都發生在聚合內部。

+0

如果將計算列中的「不可見」封裝在一起,還是會得到相同的執行計劃/性能嗎? – AakashM 2010-06-25 16:59:40

+0

@AakashM:是的,那就是我所做的。對計算列進行索引並不能真正提高性能,只是使編寫查詢變得容易一些(我仍然需要「解開」這些值)。 – Aaronaught 2010-06-25 18:52:44

相關問題