web-dev-qa-db-ja.com

Between演算子SQL Server 2008を使用する場合のインデックス作成戦略

構造を持つ大規模なテーブル〜2500万行があります

CREATE TABLE [dbo].[rx](
            [pat_id] [int] NOT NULL,
            [fill_Date] [date] NOT NULL,
            [script_End_Date]  AS (dateadd(day,[dayssup],[filldate])) persisted,
            [drug_Name] [varchar](50) NULL,
            [days_Sup] [int] NOT NULL,
            [quantity] [float] NOT NULL,
            [drug_Class] [char](3) NOT  NULL,
            CHECK(fill_Date <=script_End_Date
PRIMARY KEY NONCLUSTERED 
(
          [clmid]
)


create clustered index ix_rx_temporal on rx(fill_date asc, script_end_date asc, pat_id asc)

このテーブルの主キーは照会されません。このテーブルは、日付範囲を含むクエリで最も頻繁に使用されます。構造のカレンダーテーブルがあります

CREATE TABLE [dbo].[Calendar](
             [cal_date] [date] PRIMARY KEY,
[Year] AS YEAR(cal_date) PERSISTED,
[Month] AS MONTH(cal_date) PERSISTED,
[Day] AS DAY(cal_date) PERSISTED,
             [julian_seq] AS 1+DATEDIFF(DD, CONVERT(DATE, CONVERT(varchar,YEAR(cal_date))+'0101'),cal_date));

私が高速化しようとしているクエリは次のとおりです。

;WITH x 
     AS ( 
        --join finds the amount of distinct drugs that a person was prescribed on a given day
        SELECT rx.pat_id, 
               c.cal_date, 
               Count(DISTINCT rx.drug_name) AS distinctDrugs 
         FROM   rx, 
                calendar AS c 
         WHERE  c.cal_date BETWEEN rx.fill_date AND rx.script_end_date 
         GROUP  BY rx.pat_id, 
                   c.cal_date), 
     y 
     AS ( 
        --makes a sequence so that contiguous dates can be grouped together as a date range 
        SELECT x.pat_id, 
               x.distinctdrugs, 
               c2.julian_seq- Row_number() 
                         OVER( 
                           partition BY x.pat_id, distinctdrugs 
                           ORDER BY x.cal_date) AS rn, 
               x.cal_date 
         FROM   x, 
                calendar AS c2 
         WHERE  c2.cal_date = x.cal_date) 
--finds the max and minimum dates which a person was taking X amoung of drugs 
SELECT y.pat_id, 
       Min(y.cal_date)    AS min_overlap, 
       Max(y.cal_date)    AS max_overlap, 
       Min(distinctdrugs) AS distinctDrugs 
FROM   y 
GROUP  BY y.pat_id, 
          rn 

次のインデックスをいくつか試しました

CREATE NONCLUSTERED INDEX [ix_rx2] ON [dbo].[rx] 
(
[drug_name] ASC,
[drug_class] ASC,
[fill_date] ASC,
[script_end_date] ASC
)

そして

CREATE NONCLUSTERED INDEX [ix_rx3] ON [dbo].[rx] 
(
[fill_date] ASC,
[script_end_date] ASC
)
INCLUDE ( [pat_id],
[drug_class],
[drug_name])

実行プランをチェックアウトするときは常に、rxテーブルのインデックスはどれも使用されていません。実行計画には他の側面がありますが、その大部分は次のようになります enter image description here

現在のクラスター化インデックスを削除し、(fill_date、admi_date)を使用してみましたが、リソースの大部分を占めている実行プランでハッシュ一致演算子に実行されないようにする方法がまだ見つかりませんこのクエリ。 rxテーブルの主キーはクエリされません。このテーブルでは、主に日付範囲を含むクエリを実行します。テーブルのインデックスにはわずかな断片化があり、インデックスの密度はすべて非常に低くなっています。このクエリを高速化するにはどうすればよいですか、またはハードウェアによって制限されますか?

5

これは完全な答えではありません。今は時間がないので、少し考えてみます。完全な答えは膨大です。詳細を知りたいとは思いません。

私はすでに数年前からさまざまな時間クエリを扱っており、その過程で多くのことを学びました。そのため、本番システムでクエリを最適化する必要はありません。 T-SQLでそれを解決しないように、私は一生懸命努力します。複雑な問題です。 Itzik Ben-Ganは、彼の最新の本のOLAP functionsに関する章を含めて、 "ギャップとアイランド"について何度か書いています。問題はギャップとアイランドのバリエーションです。

最初に、クライアントにすべてのデータを読み取り、そこでループを使用して解決することを検討します。ネットワーク経由でデータを送信する必要がありますが、ほとんどの場合、Java/C++/C#の高速ループは非常にうまく機能します。たとえば、時系列と時系列データを含むクエリに苦労したことがあります。ロジックのほとんどをクライアントに移動したとき、C#ソリューションは数倍短くなり、実行時間は20,000倍高速でした。これはタイプミスではありません-2万倍高速です

T-SQLでこのような問題を解決するには、別の問題があります。パフォーマンスが不安定になる可能性があります。クエリが複雑な場合、オプティマイザは突然別のプランを選択する可能性があり、実行速度が何倍も低下するため、再度最適化する必要があります。

あるいは、データの格納方法を変えることを検討します。今、私は2つの可能なアプローチを見ています。

最初に、間隔を格納する代わりに、次のテーブルを使用できます。

ClientId,
PrescriptionId,
DrugId,
Date

信頼できる制約を使用して、各PrescriptionIdがギャップやオーバーラップなしに日付の範囲をカバーするようにして、1つの間隔が1つの途切れのない日付のシーケンスとして格納されるようにすることができます。

注:最初のサブクエリでDISTINCTを使用していることはわかっているので、1人の人が1日に複数の処方箋から1つの薬を服用できると想定しています。簡単にするために、私はこの仮定を変更していません。それは正しい仮定ですか?はいの場合、デザインを変更する必要があります。

このテーブルを取得したら、最初のサブクエリをインデックス付きビューとして具体化できます。

SELECT ClientId,Date,COUNT_BIG(*) AS DistinctDrugs
GROUP BY ClientId,
Date

これで、2番目のサブクエリを使用してデータポイントを間隔にグループ化したり、クライアントで実行したりできます。この場合、1つの簡単なループとして解決できます。

2番目のアプローチ:間隔の代わりに、イベントのシーケンスを保存します。 2種類のイベント(間隔の開始と間隔の終了)があるはずです。私が現在の合計を保存するたびに、このイベントが発生した後のオープンイベントの数。基本的に、この現在の合計は、イベントが発生した後にアクティブな処方箋の数です。

前のアプローチと同様に、クエリを実行するたびにその場で計算するデータの多くは、このテーブルで事前に計算されています。信頼できる制約を使用して、事前計算されたデータの整合性を確保できます。

興味があれば、後で詳しく説明します。

2
A-K