2

次のようなテーブルがあります(それを呼び出しましょうaudit):

+--------------------------------------------------------------------------+
| id | recordId | status | mdate                   | type  | relatedId     |
+--------------------------------------------------------------------------+
| 1  | 3006     | A      | 2013-04-03 23:59:01.275 | type1 | 1             |
| 2  | 3025     | B      | 2013-04-04 00:00:02.134 | type1 | 1             |
| 3  | 4578     | A      | 2013-04-04 00:04:30.033 | type2 | 1             |
| 4  | 7940     | C      | 2013-04-04 00:04:32.683 | type1 | <NULL>        |
| 5  | 3006     | D      | 2013-04-04 00:04:32.683 | type1 | <NULL>        |
| 6  | 4822     | E      | 2013-04-04 00:04:32.683 | type2 | <NULL>        |
| 7  | 3006     | A      | 2013-04-04 00:06:54.033 | type1 | 2             |
| 8  | 3025     | C      | 2013-04-04 00:06:54.033 | type1 | 2             |

...そして何百万行も続きます。そして、別のテーブルを呼び出しますrelated:

+-------------+
| id | source |
+-------------+
| 1  | src_X  |
| 2  | src_Y  |
| 3  | src_Z  |
| 4  | src_X  |
| 5  | src_X  |

...そして何十万行も続きます。

両方のテーブルにこれらよりも多くの列がありますが、問題を説明するために必要なのはこれだけです。列がテーブルrelatedIdに結合されrelatedます。 recordIdも別のテーブルに結合しaudit、同じrecordId.

次の出力を生成するクエリを作成しようとしています。

+-----------------+
| source  | count |
+-----------------+
| src_X   | 1643  |
| src_Y   | 255   |
| NULL    | 729   |
+-----------------+

カウントは、audit指定されたtype(例: "type1") を持ち、一連のステータス (例: "A", "B", "C") 内にあるレコードの数です。これらのステータスは、外部結合されrelated、 によってグループ化されsourceます。

キャッチはaudit、特定の日付範囲内のレコードのみを含めたいこととauditrelatedrecordId. typeさらに、 andの条件に一致するレコードを無視したいが、日付の範囲よりも古いstatus同じエントリがあります。recordId

したがって、上記のデータ例から明確にするために、 のタイプtype1と のステータス値がから"A", "B", "C"までの日付範囲で必要である2013-04-04とし2013-04-05ます。行 2 と 4 がカウントに含まれます。行 3 は、 が正しくないため除外されtypeます。行 5 はステータスが正しくないため除外されます。ステータスとタイプの両方が正しくないため、行 6 は除外されます。行 1 は日付範囲外であるため除外されます。行 7 も除外されます。これは、ステータスとタイプの基準に一致する別の行 (行 1) がrecordId日付範囲の開始よりも古いためです。行 8 と行 2 の両方が同じrecordIdで基準に一致するため、行 8 は除外されますが、範囲内の 2 つのレコードのうち最も古いレコードのみがカウントされます。

つまり、特定の recordId のエントリがテーブルに初めて表示され、対象の日付範囲内にある場合のみカウントしたいと考えています。

私たちは次のことを考え出しました:

WITH data (recordId, id) AS (
    SELECT a.recordId, MIN(a.id)
    FROM audit a
    WHERE a.status in ('A','B','C')
        AND type = 'type1'
    GROUP BY a.recordId
)
SELECT r.source, COUNT(*)
FROM data d
    JOIN audit a ON d.id = a.id
    LEFT JOIN related r ON a.relatedId = r.id
WHERE a.mdate >= '2013-04-04 00:00:00.000'
    and a.mdate < '2013-04-05 00:00:00.000' 
GROUP BY r.source

これは MSSQL Server 2008 で実行され、現在は監査テーブル ID が自動生成されるという事実に依存しています。id はレコードが挿入された時点で生成され、mdate は挿入タイムスタンプでもあり、一度挿入されたレコードは決して更新されないので、これで問題ないと思います。このクエリは、限られた一連のテスト データに対して正しい出力を提供しているように見えますが、セカンドオピニオンを期待していました。

  • このクエリは大丈夫ですか?
  • そのパフォーマンスを改善できますか?
4

1 に答える 1

4

この関数を使用して、ROW_NUMBER()RecordId と mDate に基づいてレコードをランク​​付けし、指定した日付の間に最初に出現する場所に結果を制限できます。

WITH data  AS 
(   SELECT  a.relatedId, a.mdate, rn = ROW_NUMBER() OVER(PARTITION BY a.RecordId ORDER BY a.mdate)
    FROM    audit a
    WHERE   a.status in ('A','B','C')
    AND     type = 'type1'
)
SELECT  r.source, [Count] = COUNT(*)
FROM    data d
        LEFT JOIN related r 
            ON d.relatedId = r.id
WHERE   d.rn = 1
AND     d.mdate >= '2013-04-04 00:00:00.000'
AND     d.mdate < '2013-04-05 00:00:00.000' 
GROUP BY r.source;

これが現在のソリューションよりも優れたパフォーマンスを発揮するかどうかはわかりませんが、時系列の挿入に依存する問題は解決します。時系列の挿入が問題にならない場合は、クラスター化されたキーでの並べ替えが高速になるためORDER BY、関数内でROW_NUMBER()ID を使用するように変更できます。

パフォーマンス チューニングは、外から見て行うのは非常に困難です。それを推測するためにも、関連するテーブルのインデックスとクエリの実行プランを確認する必要があります。次に、ボトルネックを特定し、どのインデックスがパフォーマンスを向上させる可能性があるかを特定できます。

この SQL Fiddleは、2 つのクエリ (私のものとあなたのもの) が同じ結果になることを示していますが、IO 統計を見ると、クエリについて次のように表示されます。

(2 row(s) affected)
Table 'Related'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Audit'. Scan count 2, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

ROW_NUMBER() を使用すると、次のようになります。

(2 row(s) affected)
Table 'Related'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Audit'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

重要な要因は、論理読み取りが 1 つ少ないことです。実行計画をざっと見てみると、ROW_NUMBER() ソリューションは分岐が 1 つ少なく、バッチ コストの 37% と見積もられているのに対し、あなたのソリューションは 63% であるため、この小さなデータ セットではパフォーマンスが低下しているように見えます。改善。

ここに画像の説明を入力

ただし、このような小さなデータのサンプルからわかることは限られています。一部のソリューションはうまくスケーリングできず、前述のように、データのサイズと分布によって異なります。私の提案は、さまざまなソリューションを試し、IO 統計と実行計画を調べてボトルネックを見つけることです。

たとえば、CTE の実行計画を見ると、これはクエリのクエリ コストの 50% を占めています。

ここに画像の説明を入力

このインデックスを追加すると:

CREATE INDEX IX_Audit_ALL ON Audit (recordId, MDate, RelatedID, status, type)

これをクエリ コストの 18% まで削減することができました。

ここに画像の説明を入力

ただし、実際には、詳細を知らなくても、このインデックスが (a) このクエリでデータを処理するのに役立ち、(b) 挿入/更新の速度が低下してデータベースに他の問題が発生しないとは言えません。

于 2013-06-26T14:01:01.170 に答える