2

まず、背景をいくつか。

スタッフが注文に関する請求データをアプリに入力し、それをSQL Server 2000データベースに保存する注文処理システムがあります。このデータベースは実際の課金システムではありません。夜間のバッチ プロセスを介してレコードをメインフレーム システムに実行できるようにするための保管場所にすぎません。

このバッチ プロセスは、外部ベンダーが提供する缶詰のサード パーティ パッケージです。その役割の一部は、拒否されたすべてのレコードのレポートを提供することです。拒否レポートは手動で処理されます。

残念ながら、サードパーティのソフトウェアはすべてのエラーをキャッチできないことが判明しました。メインフレームからデータベース内の別のテーブルにデータをプルバックし、拒否された料金をさらに別のテーブルにロードする別のプロセスがあります。

次に監査プロセスが実行され、スタッフが最初に入力したすべての内容がどこかで説明できるようになります。この監査は、実行する SQL クエリの形式をとり、次のようになります。

SELECT *
FROM [StaffEntry] s with (nolock)
LEFT JOIN [MainFrame] m with (nolock)
    ON m.ItemNumber = s.ItemNumber 
        AND m.Customer=s.Customer 
        AND m.CustomerPO = s.CustomerPO -- purchase order
        AND m.CustPORev = s.CustPORev  -- PO revision number
LEFT JOIN [Rejected] r with (nolock) ON r.OrderID = s.OrderID
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND r.OrderID IS NULL AND m.MainFrameOrderID IS NULL

もちろん大幅に修正されていますが、重要な部分は表現されていると思います。問題は、このクエリの実行に時間がかかりすぎていることです。私はそれを高速化する方法を見つけようとしています。

StaffEntry問題は、テーブルからテーブルへのJOIN であると確信していMainFrameます。どちらも当初 (このシステムでは 2003 年) からのすべての注文のデータを保持しているため、少し大きくなる傾向があります。テーブルで使用されているOrderIDとの値は、メインフレームにインポートされたときに保持されません。そのため、その結合はもう少し複雑になります。そして最後に、存在しないテーブル内のレコードを探しているので、JOIN を実行した後、where 句に醜いものがあります。EntryDateStaffEntryMainFrameIS NULL

このStaffEntryテーブルは、EntryDate (クラスター化) によって索引付けされ、Customer/PO/rev で個別に索引付けされます。 MainFrame顧客とメインフレームの課金番号 (クラスタ化されている、これは他のシステムに必要) によって索引付けされ、顧客/PO/Rev ごとに個別に索引付けされます。 Rejectedはまったく索引付けされていませんが、サイズは小さく、テストでは問題ではないことが示されています。

それで、その関係を表現できる別の(できればもっと速い)方法があるかどうか疑問に思っていますか?

4

7 に答える 7

5

まず、2 番目の LEFT JOIN を取り除くことができます。

とにかく、あなたの WHERE はすべての一致を削除していました...たとえば、S.OrderID が 1 で、値が 1 の R.OrderID があった場合、WHERE の IS NULL 強制はそれを許可しません。したがって、正しく読み取っていれば、s.OrderID IS NULL のレコードのみが返されます...

第 2 に、大量のデータを処理している場合は、通常、NOLOCK テーブル ヒントを追加しても問題はありません。あちこちでダーティリードの可能性を気にしないと仮定すると:-P ただし、通常はリスクを冒す価値があります。

SELECT *
FROM [StaffEntry] s (nolock)
LEFT JOIN [MainFrame] m (nolock) ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order
    AND m.CustPORev = s.CustPORev  -- PO revision number
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND s.OrderID IS NULL

最後に、あなたの質問の一部が私にはあまり明確ではありませんでした...

「存在しない MainFrame テーブル内のレコードを探しているので、JOIN を実行した後、where 句に醜い IS NULL があります。」

わかりました...しかし、それらのメインフレーム テーブル レコードが存在しない場所だけに制限しようとしていますか? もしそうなら、あなたはそれを WHERE でも表現したいと思うでしょう?だから、このようなもの...

SELECT *
FROM [StaffEntry] s (nolock)
LEFT JOIN [MainFrame] m (nolock) ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order
    AND m.CustPORev = s.CustPORev  -- PO revision number
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND s.OrderID IS NULL AND m.ItemNumber IS NULL

それが元のステートメントで意図していたものである場合、おそらく s.OrderID IS NULL チェックを取り除くことができますか?

于 2008-11-07T16:50:21.043 に答える
1

LEFT JOIN [Rejected] r with (nolock) ON r.OrderID = s.OrderID を RIGHT MERGE JOIN に変更してみてください:

SELECT ...
FROM [Rejected] r
     RIGHT MERGE JOIN [StaffEntry] s with (nolock) ON r.OrderID = s.OrderID
     LEFT JOIN [MainFrame] m with (nolock) ON....
于 2008-11-07T23:20:23.593 に答える
1

クエリの変更を検討する前に、すべてのテーブルに、このクエリと他のすべての重要なクエリの両方にとって意味のあるクラスター化インデックスがあることを確認する必要があります。テーブルにクラスター化されたインデックスを配置することは、SQL Server で適切なパフォーマンスを確保するために不可欠です。

于 2008-11-07T16:49:11.293 に答える
1

これは意味がありません:

SELECT *
FROM [StaffEntry] s
LEFT JOIN [MainFrame] m ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order
    AND m.CustPORev = s.CustPORev  -- PO revision number
LEFT JOIN [Rejected] r ON r.OrderID = s.OrderID
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND r.OrderID IS NULL AND s.OrderID IS NULL

if s.OrderID IS NULL, thenr.OrderID = s.OrderIDが真になることはないため、 からの行[Rejected]が含まれることはありません。

SELECT *
FROM [StaffEntry] s
LEFT JOIN [MainFrame] m ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order
    AND m.CustPORev = s.CustPORev  -- PO revision number
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND s.OrderID IS NULL

投稿したコードが正しいと確信していますか?

于 2008-11-07T16:49:54.430 に答える
1

Kasperjj が提案したことに加えて (これが最初にあることに同意します)、一時テーブルを使用してデータ量を制限することを検討することもできます。さて、誰もが一時テーブルに近づかないように言っていることを知っています。そして、私は通常そうしますが、この方法で結合するデータの量を大幅に縮小できるため、試してみる価値がある場合があります。これにより、クエリ全体が高速になります。(もちろん、これは結果セットをどれだけ縮小できるかによって異なります。)

私の最終的な考えは、クエリをまとめるためのさまざまな方法を試す必要がある場合があるということです。ここにいる誰もが答えを出すには変数が多すぎるかもしれません.... 一方で、ここの人々は賢いので、私が間違っているかもしれません.

頑張ってください!

よろしく、フランク

PS: この一時テーブルの方法を試してみたい場合は、一時テーブルでさまざまなインデックスと主キーを試してみる必要があることを忘れていました。データの量によっては、インデックスと PK が役立ちます。

于 2008-11-07T16:56:04.087 に答える
1

すべてのテーブルのインデックス作成が重要になります。結合で使用される [MainFrame] 列のインデックス作成で多くのことができない場合は、[MainFrame] (および [Rejected]) で検索する行を事前に制限することもできます。 PK) 日付範囲を指定する - 日付のウィンドウがほぼ同じである必要がある場合。これにより、その結合の右側が削減される可能性があります。

JOINまた、実行計画を見て、どれが実際に最も高価であるmr、またはどちらか一方のみでクエリをベンチマークすることにより、単純なブラックボックス評価も行います。m複数の列と有用なインデックスがないためだと思います。

範囲の数日または数か月以内に m.EntryDate を使用できます。しかし、メインフレームに既にインデックスがある場合、問題はなぜそれらが使用されていないのか、または使用されている場合、なぜパフォーマンスが非常に遅いのかということです。

于 2008-11-07T17:01:57.273 に答える
0

更新:
それがまだ明らかでない場合は、元の質問のコードを間違えました。これは現在修正されていますが、残念ながら、ここでのより良い応答のいくつかは実際には完全に間違った方向に進んでいることを意味します。

また、統計の更新もいくつかあります。で使用するデータ範囲を厳しく制限することで、クエリを適切かつ迅速に実行できますStaffEntry.EntryDate。残念ながら、私はそれを行うことができるだけです。なぜなら、それを長い道のりで実行した後、私が気にかけている日付を正確に知っているからです。普段は事前に知りません。

最初の実行からの実行プランでは、テーブルでのクラスター化インデックススキャンに78%のコスト、StaffEntryテーブルのインデックスシークに11%のコスト、MainFrameそして結合自体に0%のコストが示されました。狭い日付範囲を使用して実行すると、インデックスシークのStaffEntry場合は1%、「MainFrame」のインデックスシークの場合は1%、テーブルスキャンの場合は93%に変更されますRejected。これらは「実際の」計画であり、推定ではありません。

于 2008-11-07T17:31:10.397 に答える