車AutoData
の履歴データと、クラスター化キーCas
(DateTime)+ GCom
(Car ID)を組み合わせたテーブルがあります。 1つのレコードには、燃料レベル、車両状態などのさまざまな指標が含まれています。
AutoData
テーブルの1台の車の個々のレコード間の間隔は不規則で、120秒、数秒、数時間などの場合があります。レコードを表示用に正規化して、30ごとに1つのレコードを表示する必要があります。秒。
次のスクリプトがあります。
DECLARE @GCom int = 2563,
@Od DateTime2(0) = '20170210',
@Do DateTime2(0) = '20170224'
--Create a table with intervals by 30 seconds
declare @temp Table ([cas] datetime2(0))
INSERT @temp([cas])
SELECT d
FROM
(
SELECT
d = DATEADD(SECOND, (rn - 1)*30, @Od)
FROM
(
SELECT TOP (DATEDIFF(MINUTE, @Od, @Do)*2)
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
--Create temp table
CREATE TABLE #AutoData (
[Cas] [datetime2](0) NOT NULL PRIMARY KEY,
[IDProvozniRezim] [tinyint] NOT NULL,
[IDRidic] [smallint] NULL,
[Stav] [tinyint] NOT NULL,
[Klicek] [bit] NOT NULL,
[Alarm] [bit] NOT NULL,
[MAlarm] [tinyint] NOT NULL,
[DAlarm] [bit] NOT NULL,
[Bypass] [bit] NOT NULL,
[Lat] [real] NULL,
[Lon] [real] NULL,
[ObjemAktualni] [real] NOT NULL,
[RychlostMaxV1] [real] NOT NULL,
[RychlostV2] [real] NOT NULL,
[Otacky] [smallint] NOT NULL,
[Nadspotreba] [real] NOT NULL,
[Vzdalenost] [real] NOT NULL,
[Motor] [smallint] NOT NULL
)
--Populate the temp table selecting only relevant AutoData records
INSERT INTO #AutoData
SELECT [Cas]
,[IDProvozniRezim]
,[IDRidic]
,[Stav]
,[Klicek]
,[Alarm]
,[MAlarm]
,[DAlarm]
,[Bypass]
,[Lat]
,[Lon]
,[ObjemAktualni]
,[RychlostMaxV1]
,[RychlostV2]
,[Otacky]
,[Nadspotreba]
,[Vzdalenost]
,[Motor]
FROM AutoData a
WHERE a.GCom = @GCom AND a.cas BETWEEN @Od AND @do
--Select final data
SELECT t.cas, ad.malarm, ad.IDProvoznirezim, ad.Otacky, ad.motor, ad.objemAktualni, ad.Nadspotreba
FROM @temp t
OUTER APPLY (
SELECT TOP 1 stav, malarm, otacky,motor, objemAktualni, Nadspotreba, IDProvoznirezim FROM #AutoData a
WHERE DATEDIFF(SECOND, a.cas, t.cas)<=CASE WHEN Motor>120 THEN Motor ELSE 120 END
AND DATEDIFF(SECOND, a.cas, t.cas)>-30
ORDER BY CASE WHEN DATEDIFF(SECOND, a.cas, t.cas)>0 THEN DATEDIFF(SECOND, a.cas, t.cas) ELSE (DATEDIFF(SECOND, a.cas, t.cas)*-1) +120 END
) ad
DROP TABLE #AutoData
最初は、最後の選択に条件WHERE a.GCom = @GCom AND a.cas BETWEEN @Od AND @do
を配置して、1つのテーブル変数@tempのみでスクリプトを記述しようとしました。スクリプトの実行には39秒かかりました。
上記のスクリプトに示すように、#AutoData
一時テーブルを使用してデータサブセットを一時テーブルにプリロードすると、5秒になりました。
次に、@AutoData
の代わりにテーブル変数#AutoData
を使用しようとしましたが、再び非常に長くかかりました(22秒)。
この例では、@temp
テーブルには40320レコードがあり、#AutoData
テーブルには1904レコードがあります。しかし、驚いたことに、#temp
変数の代わりに@temp
テーブルを使用しただけで、実行が再び遅くなりました。
一時テーブル/変数を使用した場合と使用しない場合の違いに驚きました。どうやらSQL Serverはそれ自体ではOUTER APPLY句の内部を最適化できませんでした。
しかし、なぜテーブル変数と一時テーブルを使用する場合にそのような大きな違いがあるのでしょうか?それを試すだけでなく、他に何を使用するかを知る方法はありますか?
キーはあなたの質問のこの部分にあります:
@tempテーブルには40320レコードがあります
実行プランで、@ tempテーブルのスキャンの上にマウスを置きます。推定行数と実際の行数を比較します。 (計画を http://PasteThePlan.com に投稿する場合は、より具体的な詳細をお知らせします。免責事項:これは私の会社のサイトです。)
推定行数が本当に少ないことがわかります。
SQL Serverは、1〜3行がテーブル変数から返されると推定します(SQL Serverのバージョン、カーディナリティエスティメータ、トレースフラグなどによって異なります)。これは、SQL Serverが作業量を過小評価しているため、実際に悪い実行計画を与えます。他のテーブル、どれだけのメモリを確保する必要があるかなどが必要です。
より正確な見積もりを取得する最も一般的な2つの方法を次に示します。
私がライブで実行しているのを見るには、1時間 ブレントチューンクエリを見る を見てください(免責事項:それは私です。自分のビデオにリンクしています)。ここで、テーブル変数を使用するスタックオーバーフロークエリを実行しています。 SQL Rally Norwayの聴衆の前でライブチューニングしてください。
#tempを使用すると、クエリプランナーがより効率的になります。テーブル変数では、最初の数行のみが考慮されます。
テーブル変数(および使用する場合は#temp)は、主キーを宣言することでメリットを得られる可能性があります。
#AutoDataにキーを配置し、必要な行のみを入力します。
行を追加するときにキーでソート。
以下はrow_number()で最適化できると思います
SELECT t.cas
, ad.malarm, ad.IDProvoznirezim, ad.Otacky
, ad.motor, ad.objemAktualni, ad.Nadspotreba
FROM @temp t
OUTER APPLY ( SELECT TOP 1 malarm, IDProvoznirezim, Otacky
, motor, objemAktualni, Nadspotreba
FROM #AutoData a
WHERE DATEDIFF(SECOND, a.cas, t.cas) <= CASE WHEN Motor > 120 THEN Motor ELSE 120 END
AND DATEDIFF(SECOND, a.cas, t.cas) > -30
ORDER BY CASE WHEN DATEDIFF(SECOND, a.cas, t.cas) > 0 THEN DATEDIFF(SECOND, a.cas, t.cas)
ELSE DATEDIFF(SECOND, t.cas, a.cas) + 120 END
) ad
これはrow_number()としての試みです
select * from
( SELECT t.cas
, a.malarm, a.IDProvoznirezim, a.Otacky
, a.motor, a.objemAktualni, a.Nadspotreba
, row_nunber() over (partition by t.cas
ORDER BY CASE WHEN DATEDIFF(SECOND, a.cas, t.cas) > 0 THEN DATEDIFF(SECOND, a.cas, t.cas)
ELSE DATEDIFF(SECOND, t.cas, a.cas) + 120 END) rn
FROM @temp t -- with primay key t.cas order by
join AutoData a
on a.GCom = @GCom
AND a.cas BETWEEN @Od AND @do
AND DATEDIFF(SECOND, a.cas, t.cas) <= CASE WHEN Motor > 120 THEN Motor ELSE 120 END
AND DATEDIFF(SECOND, a.cas, t.cas) > -30
) ad
where ad.rn = 1