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)) を使用してこれを実行できることはわかっていますが、これには完全なインデックス スキャンと非常にコストのかかる並べ替えが必要なため、効率が大幅に低下します。ずっと書き続けるのもかなり厄介です。

2 つの CTE またはネストされたサブクエリを使用すると、もう少し効率的です。1 つはMAX(TransactionDate)perを検索しCustomerID、もう 1 つは を検索しますMAX(TransactionID)。繰り返しますが、機能しますが、2 番目の集計と結合が必要です。これは、ROW_NUMBER()クエリよりもわずかに優れていますが、パフォーマンスに関してはかなり苦痛です。

CLR User-Defined Aggregate の使用も検討しており、必要に応じてフォールバックしますが、展開を簡素化するために、可能であれば純粋な SQL ソリューションを見つけたいと思います (これ以外の場所では SQL-CLR は必要ありません)。事業)。

質問は、具体的には次のとおりです。

最新のperを返し、最新 の の最大値として定義されたクエリを作成し、通常の/クエリと同等のパフォーマンスを実現するクエリを作成することは可能ですか?TransactionIDCustomerIDTransactionIDTransactionDateMAXGROUP 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

オプティマイザーに最初に派生テーブルを計算させるこのようなものはどうですか。私のテストでは、これは 2 つの 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 に答える