如何有效地计算一列的MAX,按另一列排序?

时间:2010-06-25 14:38:05

标签: sql-server sql-server-2008 aggregate composite-key

我有一个类似于以下(简化)的表模式:

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过程),因此我没有很多控制权。它们插入的顺序。即使我这样做,交易也可能会过时,因此请注意的重要事项是任何给定TransactionID的最大customer不一定是最近的交易。 < / p>

事实上,最近的交易是日期 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查询相当的效果计划?

(换句话说,计划中唯一重要的步骤应该是索引扫描和流聚合。多次扫描,排序,连接等可能太慢。)

5 个答案:

答案 0 :(得分: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

答案 1 :(得分: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

答案 2 :(得分:0)

免责声明:大声思考:)

你能否拥有一个索引的计算列,它将TransactionDate和TransactionID列组合成一个表单,这意味着找到最新的事务只是找到该单个字段的MAX?

答案 3 :(得分: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

答案 4 :(得分: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)相同的操作 - 显然,这很有意义,因为所有连接都发生在聚合本身内。