web-dev-qa-db-ja.com

初心者のためのカーソル置換

カーソルの一般的な置換方法を教えてください。私が見ているカーソルの一般的な実装は

DECLARE @variable INT, @sqlstr NVARCHAR(MAX)

DECLARE cursor_name CURSOR
FOR select_statement --essentially to get an array for @variable 
                     --usually it's a subset of unique ids for accounts, clients, parts, etc

OPEN cursor_name
FETCH NEXT FROM cursor_name INTO @variable
WHILE @@FETCH_STATUS = 0
BEGIN
     SET @sqlstr = N'
     /* some query that uses '+ str(@variable) +' to do dirty work
     such as: go through all our accounts, if it''s some subset (possible new cursor), 
     go through those accounts and connect this way, 
     map those fields and add it to our big uniform table */
     '

     EXEC sp_executesql @sqlstr
FETCH NEXT FROM cursor_name INTO @variable
END

CLOSE cursor_name
DEALLOCATE cursor_name

非常に多くの人々がアンチカーソルであるので( に同意する:SO:なぜ人々はカーソルを嫌うのか )一般的な実装の一般的な置き換えは何ですか(できればSQLサーバー)?

6
undrline

それは依存します™

1つまたは複数のカーソルを回避する機能は、このカーソル内で実行される内容によって異なります。その中で何が起こっているのかを知らなければ、伝える方法はありません。回避策がない可能性があり、行ごとに処理する必要があります。

以下にいくつかの例を示します。

セットでは機能しない

この例は最も基本的な例であり、データセット全体またはデータセットの一部を一度にクエリできますが、カーソルが作成され、行ごとにデータをクエリしています。これを置き換える一般的なものは、JOIN、_CROSS APPLY_/_OUTER APPLY_などです。

次のデータセットを検討してください:

_CREATE TABLE dbo.Lotr(LotrId int, CharacterName varchar(255), Val varchar(255));
CREATE TABLE dbo.LotrAttributes(LotrATtributeId int, LotrId int, AttrVal varchar(255));

INSERT INTO dbo.Lotr(LotrId,CharacterName,Val)
VALUES(1,'Frodo','Ring')
,(2,'Gandalf','Staff');

INSERT INTO dbo.LotrAttributes(LotrId,LotrATtributeId,AttrVal)
VALUES(1,1,'RingAttribute1')
,(1,2,'RingAttribute2')
,(1,3,'RingAttribute3')
,(2,4,'StaffAttribute1')
,(2,5,'StaffAttribute2');
_

Lotrテーブルをループすることで、各レコードを検索して個別に一致させることができます。

カーソル:

_DECLARE @LotrID int
DECLARE C CURSOR FOR SELECT LotrId from dbo.Lotr;
OPEN C
FETCH NEXT FROM C INTO @LotrID;
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT LotrATtributeId from dbo.LotrAttributes where LotrId = @LotrID;
FETCH NEXT FROM C INTO @LotrID;
END
CLOSE C
DEALLOCATE C
_

結果は2つの結果セットになります

_LotrATtributeId
1
2
3
LotrATtributeId
4
5
_

この_inner join_を使用すると、1つの結果セットと同じ結果が得られます。

_SELECT LotrATtributeId from dbo.Lotr L
INNER JOIN dbo.LotrAttributes LA 
ON L.LotrId = LA.LotrId;

LotrATtributeId
1
2
3
4
5
_

文字列操作

一般的なのは、カーソル内の文字列操作を置き換えるためにFOR XML PATH('')を使用することです。

データセット

_CREATE TABLE dbo.Lotr(LotrId int, CharacterName varchar(255), Val varchar(255));
CREATE TABLE dbo.LotrAttributes(LotrATtributeId int, LotrId int, AttrVal varchar(255));

INSERT INTO dbo.Lotr(LotrId,CharacterName,Val)
VALUES(1,'Frodo','Ring');

INSERT INTO dbo.LotrAttributes(LotrId,LotrATtributeId,AttrVal)
VALUES(1,1,'RingAttribute1')
,(1,2,'RingAttribute2')
,(1,3,'RingAttribute3');
_

文字列操作付きのダブルカーソル

_DECLARE @LotrId int, @CharacterName varchar(255), @Val varchar(255)
DECLARE @LotrATtributeId int, @AttrVal varchar(255)
DECLARE C CURSOR FOR
SELECT LotrId,CharacterName, Val FROM dbo.Lotr
OPEN C
FETCH NEXT FROM C INTO @LotrId,@CharacterName,@Val
WHILE @@FETCH_STATUS = 0
BEGIN

        SET @CharacterName +='|'+ @Val

        DECLARE D CURSOR FOR
        SELECT LotrATtributeId, AttrVal FROM dbo.LotrAttributes where LotrId = @LotrId
        OPEN D
        FETCH NEXT FROM D INTO @LotrATtributeId,@AttrVal
        WHILE @@FETCH_STATUS = 0
        BEGIN
        SET @CharacterName +='['+@AttrVal+ '],'

        FETCH NEXT FROM D INTO @LotrATtributeId,@AttrVal
        END
        CLOSE D 
        DEALLOCATE D

FETCH NEXT FROM C INTO @LotrId,@CharacterName,@Val
END
CLOSE C
DEALLOCATE C
SELECT LEFT(@CharacterName,len(@charactername)-1);
_

結果

_(No column name)
Frodo|Ring[RingAttribute1],[RingAttribute2],[RingAttribute3],
_

FOR XML PATH( '')を使用してカーソルを削除する

_SELECT L.Charactername +'|'+ L.Val + (SELECT stuff((SELECT ','+QUOTENAME(AttrVal) FROM dbo.LotrAttributes LA WHERE LA.LotrId = L.LotrId FOR XML PATH('')), 1, 1, ''))
FROM
dbo.Lotr L;
_

*

ここでの実際の回避策は、データがこのように表示される理由を理解し、アプリケーションを変更することです。..この形式でデータを必要としないように、どこかに保存します。

あなたの手が結ばれている場合、これは次善の策です。


別のテーブルのIDに基づいて一時テーブルに上位10個の値を挿入する

データ

CREATE TABLE dbo.sometable(InsertTableId int、val varchar(255)); CREATE TABLE dbo.Top10Table(Top10TableId int、InsertTableId int、val varchar(255));

_INSERT INTO dbo.sometable(InsertTableId,val)
VALUES(1,'bla')
,(2,'blabla');
INSERT INTO dbo.Top10Table(Top10TableId,InsertTableId,Val)
VALUES(1,1,'WUW')
,(2,1,'WUW')
,(3,1,'WUW');
_

カーソル

_CREATE TABLE #Top10Values(Top10TableId int, InsertTableId int, val varchar(255))

    DECLARE @InsertTableId int;
    DECLARE C CURSOR FOR select InsertTableId from dbo.sometable;
    OPEN C
    FETCH NEXT FROM C INTO @InsertTableId;
    WHILE @@FETCH_STATUS =0
    BEGIN
    INSERT INTO #Top10Values(Top10TableId,InsertTableId,val)
    SELECT top(10) Top10TableId,InsertTableId,Val FROM dbo.Top10Table 
    where InsertTableId = @InsertTableId
    ORDER BY Top10TableId 

    FETCH NEXT FROM C INTO @InsertTableId;
    END
    CLOSE C
    DEALLOCATE C

    SELECT * FROM  #Top10Values;
    DROP TABLE #Top10Values;
_

結果

_Top10TableId    InsertTableId   val
1   1   WUW
2   1   WUW
3   1   WUW
_

カーソルを_CROSS APPLY_とCTEで置き換える

_CREATE TABLE #Top10Values(Top10TableId int, InsertTableId int, val varchar(255));
;WITH CTE 
AS
(
select InsertTableId  from dbo.sometable
)

INSERT INTO #Top10Values(Top10TableId,InsertTableId,val)
SELECT  T1T.Top10TableId,T1T.InsertTableId,T1T.Val 
FROM 
CTE
CROSS APPLY (SELECT TOP (10) Top10TableId,InsertTableId,Val from dbo.Top10Table T1T
WHERE T1T.InsertTableId = CTE.InsertTableId
) T1T ;

SELECT * FROM  #Top10Values;
DROP TABLE #Top10Values;
_

その他の例

  • _CROSS APPLY_ here を使用して、カーソルを置き換えてサプライヤーごとの動的なアイテムのセットを選択する例。
  • ウィンドウ関数を使用してカーソルを置き換える例 here

時々他に選択肢がない

セットで作業できず、行ごとの処理を行う必要がある場合でも、カーソルを最適化できます。

カーソルの高速化における最大の変更点の1つは、カーソルに_LOCAL FAST_FORWARD_を追加することです。

_DECLARE C CURSOR LOCAL FAST_FORWARD FOR SELECT LotrId from dbo.Lotr
_

LOCAL&_FAST_FORWARD_などのカーソル設定を使用した場合と使用しない場合のパフォーマンスの違いの可能性について説明している@AaronBertrandによる このブログ投稿 をご覧ください。

6
Randi Vertongen

「一般的な置き換え」はありません。ここでは「汚い仕事」をすべて隠しているため、この場合にspecificの置き換えがあるかどうかを判断するのは困難です。カーソルを使用するか、whileループを使用するか、またはを処理するセットベースのプロセスに変換するその他の反復プロセスを使用するかに関わらず、一度に1行ずつ一連の行を処理する特定のケースがあります。すべて行を一度にはるかに優れています。ただし、ストアドプロシージャの実行や行ごとの動的SQLの実行、複数のデータベースにわたる同じクエリなど、一度に1行ずつ実行する必要のあるものもあります。

カーソルであるかどうかに関係なく、宣言とリンクの問題は、declare cursorを使用しても他のループ構造体を使用しても同じです( this post を参照)。また、実行する必要がある場合は関係ありません。とにかく一度に1行ずつ行われます。したがって、このカーソルの動作に関する特定の詳細を提供すると、カーソルを削除する方法(またはできないこと)に関するアドバイスが得られる可能性がありますが、適用できる魔法のようなすべてのカーソルを削除するアプローチを検索すると、すべてのシナリオにあなたのためにかなりイライラするでしょう。

言語IMHOを入力する新しい人々に対する一般的なアドバイスは、行のセットに対して、常に何をする必要があるかを考えることですセットの各行に。言語の違いは微妙ですが、重要です。問題を個々の行の束ではなくデータのセットとして考える場合、デフォルトでカーソルを使用する可能性は低くなります。しかし、SQL Serverがそのように機能するように最適化されていないことを単に教える以外に、反復型が最良/唯一の方法であるさまざまな種類のプログラミングからのものである場合、それを明白にする方法や、自動。

あなたの質問はまだ一般的な交換を求めています、そして私はまだそのようなことはないと信じています。

6
Aaron Bertrand

ダグレーンは、YouTubeにある「T-SQLレベルアップ」と呼ばれる一連のビデオを制作しました。シリーズの一部では、次のようなカーソルを削除する一般的な方法について説明します。

  • すべてのカーソル言語(カーソルの宣言、オープン、フェッチ、while、クローズ、割り当て解除など)および他の変数宣言を削除します
  • セットベースの操作を組み合わせることができる場所を特定します(SELECTで後で使用されるINSERTによって入力された変数は、INSERT INTO...SELECTステートメント、たとえば)
  • 条件付きロジック(IF...ELSE)_WHERE句、CASEステートメント、サブクエリなどに

ここで他の素晴らしい答えが指摘しているように、これに対する特効薬はありません。しかし、私の意見では、これらのビデオは問題を解決するための非常に直感的なアプローチです。

Dougは、各部分で複雑さが増している3つのカーソルの置換を行っています。ビデオで全体の取引がよりよく見られるため、私は視聴することを強くお勧めします。

1
Josh Darnell