3

私は現在、特に複雑なユースケースに取り組んでいます。以下の簡略化:)

第 1 に、クライアント レコードは、サービスのコレクションと多対 1 の関係にあります。つまり、1 つのクライアントに複数のサービスが関連付けられている場合があります。

トリガー内で、特定の基準に基づいてクライアントの ID を返すクエリを作成しています。基準は次のとおりです。

  1. タイプ B のサービスが少なくとも 1 つあり、タイプ A のサービスが存在しない場合は、id を返します。
  2. タイプ C のサービスが少なくとも 1 つあり、タイプ B または A のサービスが存在しない場合は、id を返します。
  3. 少なくとも 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')))

where[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 のパフォーマンスに関する回答には非常に感謝しています。

4

3 に答える 3

3

私はあなたの質問に対する回答を書いていましたが、その間に要件を変更しましたが、私のソリューションを特定のニーズに変換するのに問題はないはずです..

しかし、最初から始めましょう。SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')とにかくサーバーによってキャッシュされていないと確信しています。それほどスマートではありません;)したがって、メインクエリで複数回計算されます。

したがって、最初の最適化はその方向に進む必要があります。Get_ServicesByClientIdAndTypeが呼び出される回数を減らす必要があります。さまざまな方法でそれを行うことができます。しかし、原則として、すべてのクライアントについて、この関数のすべての可能な結果を​​計算する必要があります。これらの結果は、一時テーブルに配置するか、SQL Server 自体によって作成される仮想テーブルに配置する必要があります。

考えられるすべての結果が得られたら、それらをクライアント テーブルに結合するだけです。ただし、それらを JOIN するのは一度だけです

もちろん、多くのことと最適化のトリックは、実際の例に依存します。あなたが与えた例では、使用の必要さえありません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 を返すように実装を変更してみませんか? 時間を大幅に節約できます。

しかし、全体像を知っているのはあなただけなので、ここに書いたのはゴミかもしれません;-)

とにかく、何らかの形でお役に立てば幸いです。

于 2009-11-06T21:00:43.493 に答える
1

SQLサーバーは、パラメーター値の組み合わせごとに関数Get_ServicesByClientIdAndTypeを1回呼び出しますが、Clientsテーブルのすべての行に対して呼び出します。値の組み合わせが 3 つあるため、Client テーブルの 100 行の場合、関数の呼び出しが 300 回表示される可能性があります。

ただし、自信を持って、SQL Server Management Studio でクエリを実行し、「実行計画を表示する」オプションをオンにしてください。このようにして、クエリのどの部分が最も多くのリソースを消費しているかを簡単に検出し、その部分の最適化に集中できます。

于 2009-11-06T15:10:22.407 に答える
0

心に留めておくべきことの 1 つは、可能であれば「NOT」を避けることです。「NOT」はサーガブルではなく、インデックス作成の利点を十分に活用できません。一見すると、NOT 式を避けるように書き直す方法がわかりません。FWIW、YMMV。:-)

于 2009-11-06T15:51:22.470 に答える