SQL サブクエリ/関数のパフォーマンス
-
18-09-2019 - |
質問
私は現在、特に複雑なユースケースに取り組んでいます。以下に簡略化します:)
まず、クライアント レコードはサービスのコレクションと多対 1 の関係を持ちます。つまり、1 つのクライアントに複数のサービスが関連付けられている可能性があります。
トリガー内で、特定の条件に基づいてクライアントの ID を返すクエリを作成しています。基準は次のとおりです。
- 少なくとも 1 つのサービスがタイプ B であり、タイプ A のサービスが存在しない場合は、id を返します。
- 少なくとも 1 つのサービスがタイプ C であり、タイプ B または A のサービスが存在しない場合は、id を返します。
- 少なくとも 1 つのサービスがタイプ D であり、タイプ C、B、または A のサービスが存在しない場合は、id を返します。
私の現在のアプローチは、以下のようなクエリを作成することです
SELECT c.ClientId
FROM
Clients AS c
-- actually INNER JOIN is superfluous in this sample, but required for
-- other auxilliary criteria i have left out. illustrates relationship
-- between Clients and Services table
INNER JOIN Services AS s ON c.ClientId = s.ClientId
WHERE
-- has at least one service of type B, no A
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) OR
-- has at least one service of type C, no B, no A
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) AND
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) OR
-- has at least one service of type D, no C, no B, no A
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'D')) AND
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) AND
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')))
どこ [dbo].[Get_ServicesByClientIdAndType]
は、指定されたクライアント ID とサービス タイプに関連するサービスを返す関数です。に似ている
-- this query is actually significantly more complex than shown
-- below, but this illustrates use of parameters client id and
-- service type
SELECT s.ServiceType
FROM
Services AS s
WHERE
s.ClientId = @clientId AND
s.ServiceType = @serviceType
これがこのユースケースを表現する最適な手段であると仮定すると、機能するでしょう [dbo].[Get_ServicesByClientIdAndType]
サブクエリはキャッシュされるべきですか、それともサービスパラメータを変更すると、呼び出すたびに新しい評価が必要ですか?[私はこれを9回ほど呼び出しています!!!SQL Server 2005 を実行中]
SQL Server 2005 が結果のキャッシュなど、いくつかのサブクエリの最適化をサポートしていることは知っていますが、SQL Server の機能を最大限に活用するために、どのような状況で、またはどのようにサブクエリ [または関数] を作成すればよいのか、よくわかりません。
編集: 上記の基準を見直しましたが、何かがおかしいというしつこい感覚を手放すことができませんでした。私は頭の中でいくつかのロジックを試してみて、この[はるかに単純な]公式を思いつきました。
SELECT c.ClientId
FROM
Clients AS c
INNER JOIN Services AS s ON c.ClientId = s.ClientId
WHERE
NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')) AND
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) OR
EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) OR
EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'D')))
基本的に、C と D の場合と同様に、拒否につながる B に関するシナリオは存在しないため、どのような構成でも受け入れられます。A がどの選択にも存在しないことだけを気にします。 あー!チャーリー・ブラウン!
どちらの式もレビューのために残しましたが、ユーザー定義関数に関する SQL Server のパフォーマンスに関する回答には今でも非常に感謝しています。
解決
私はあなたの質問に対する回答を書いていて、その間に要件を変更しましたが、私のソリューションをあなたの特定のニーズに変換するのに問題はないはずです。
しかし、最初から始めましょう。私はそれをかなり確信しています SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')
とにかくサーバーによってキャッシュされません。それはそれほどスマートではありません ;) したがって、メインクエリで複数回計算されます。
したがって、最初の最適化はその方向に進む必要があります。回数を減らしたほうがいいですよ Get_ServicesByClientIdAndType
と呼ばれます。さまざまな方法でそれを行うことができます。ただし、一般的なルールとして、すべてのクライアントについてこの関数の考えられるすべての結果を計算する必要があります。これらの結果は、一時テーブルに保存するか、SQL Server 自体によって作成される仮想テーブルに保存する必要があります。
考えられるすべての結果が得られたら、それらをクライアント テーブルと結合するだけです。ただし、参加するのはあなただけです 一度.
もちろん、多くのことや最適化のコツは実際の例に依存します。あなたが与えた例では、使用する必要さえありません Get_ServicesByClientIdAndType
。これら 2 つのテーブルを単純に結合して計算を実行してみてはいかがでしょうか。
このクエリを見てください。
SELECT A.* FROM
(
SELECT C.ClientID,
SUM(CASE(S.ServiceType) WHEN 'A' THEN 1 ELSE 0 END) AS ServiceA,
SUM(CASE(S.ServiceType) WHEN 'B' THEN 1 ELSE 0 END) AS ServiceB,
SUM(CASE(S.ServiceType) WHEN 'C' THEN 1 ELSE 0 END) AS ServiceC,
SUM(CASE(S.ServiceType) WHEN 'D' THEN 1 ELSE 0 END) AS ServiceD
FROM Clients AS C
INNER JOIN Services AS s ON c.ClientId = s.ClientId
GROUP BY C.ClientID
) A
WHERE ((A.ServiceB > 0) AND (A.ServiceA = 0))
OR ((A.ServiceC > 0) AND (A.ServiceA = 0) AND (A.ServiceB = 0))
OR ((A.ServiceD > 0) AND (A.ServiceA = 0) AND (A.ServiceB = 0) AND (A.ServiceC = 0))
内部クエリではテーブルを結合します。関数は必要ないので捨てます。代わりに、クライアントごとに異なるサービスの数を計算します。次に、内部クエリの結果に対して条件を実装します。特定のセット内で指定されたサービスが発生するかどうかを確認するだけです。
結果は次のようになります。
ClientID ServiceA ServiceB ServiceC ServiceD
-------- -------- -------- -------- --------
26915 0 4 2 2
26917 0 0 1 1
26921 0 3 2 3
26927 0 4 2 4
もちろん、サービス列から最終結果を削除することもできます。私はそれが気に入っているのでこれらを含めました ;-) そして、クエリが適切に機能するかどうかを確認することができます。特定のクライアントの特定のサービス タイプの数を計算しないクエリを作成することもできます。より速く動作し、適切な結果が得られます。
また、本当に関数が必要な場合は、最初の結合が成功した後に関数が ID を返すようにその実装を変更してみてはいかがでしょうか。時間を大幅に節約できます。
しかし、全体像を知っているのはあなただけなので、ここに書いたことはすべてくだらないかもしれません ;-)
とにかく、何らかの形であなたのお役に立てれば幸いです。
他のヒント
私は、SQL Serverがクライアントテーブルの行ごとにパラメータ値の組み合わせごとに一回、あなたの関数のGet_ServicesByClientIdAndTypeを呼び出しますが、そのことを推測します。クライアントテーブル内の100行のためには、関数の呼び出し300が表示される場合がありますので、あなたは、値の3つの組み合わせを持っています。
しかし、自信があるSQL Serverの管理スタジオでクエリを実行し、オプション「ショーの実行計画」に切り替えます。簡単にあなたのクエリのどの部分を検出することができますこの方法では、その部分を最適化する上で最もリソースとconentrateを消費します。
心に留めておくべき一つのことは、すべての可能性であれば、「NOT」を回避することです。 「NOT」インデックスの完全な利点を取ることができなくなり、非検索引数可能です。一見、私はしかし、NOT表現を避けるために、それを書き換える方法が表示されません。 FWIW、YMMV。 : - )