2

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

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 过程),因此我对他们被插入。即使我这样做了,交易也可能是回溯的,所以这里要注意的重要一点是,任何给定的最大值不一定是最近的交易。TransactionIDcustomer

事实上,最近的交易是日期ID 的组合。日期不是唯一的 - 供应商经常截断一天中的时间 - 所以要获得最近的交易,我必须首先找到最近的日期,然后找到该日期的最新 ID。

我知道我可以使用窗口查询ROW_NUMBER() OVER (PARTITION BY TransactionDate DESC, TransactionID DESC)(一直写下去也很尴尬。

稍微高效的是使用两个 CTE 或嵌套子查询,一个用于查找MAX(TransactionDate)per CustomerID,另一个用于查找MAX(TransactionID). 再次,它可以工作,但需要第二次聚合和连接,这比查询稍好,ROW_NUMBER()但在性能方面仍然相当痛苦。

我也考虑过使用 CLR 用户定义的聚合,如果有必要,我会依赖它,但如果可能的话,我更愿意找到一个纯 SQL 解决方案来简化部署(在此其他任何地方都不需要 SQL-CLR项目)。

所以这个问题,具体来说是:

是否可以编写一个查询来返回最新 TransactionID的per CustomerID,定义为TransactionID最近的最大值TransactionDate,并实现与普通MAX/GROUP BY查询等效的计划?

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

4

5 回答 5

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
于 2010-06-25T14:47:28.967 回答
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
于 2010-06-25T15:45:04.313 回答
0

免责声明:大声思考:)

您能否拥有一个索引计算列,将 TransactionDate 和 TransactionID 列组合成一种形式,这意味着查找最新事务只是查找该单个字段的 MAX 的一种情况?

于 2010-06-25T14:48:06.097 回答
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
于 2010-06-25T15:50:39.490 回答
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)- 这显然是有道理的,因为所有连接都发生在聚合本身内部。

于 2010-06-25T16:04:50.380 回答