質問の背景:
標準SQLはもともと、ループや分岐、直接のジャンプといった古典的なプログラミング要素を含まない宣言型言語として設計されていますが、T-SQLやPLSQLのような拡張によって、WHILEやCASE、さらにはGOTOの模倣のような構文が利用可能になりました。
問題:
イテレーティブな操作(一件ずつ処理するループ)は、大量データを処理する際にしばしば遅延を引き起こします。これは「セットベース」のアプローチに置き換えない限り問題です。分岐、CASE、IFは非常に便利ですが、過度にネストするとコードの可読性と予測可能性が損なわれます。
解決策:
制御構造の使用は、バルク(bulk/set)処理を回避できない場合にのみ正当化されるべきです!複雑な計算の場合は、小さなループ、トリガー、またはCASEを使うことが許容されます。大規模な処理には、ウィンドウ関数やサブクエリを用いたUPDATEを使用するのが望ましいです。
コードの例(T-SQL):
DECLARE @i INT = 1 WHILE (@i <= 5) BEGIN IF @i % 2 = 0 PRINT CONCAT('偶数: ', @i) ELSE PRINT CONCAT('奇数: ', @i) SET @i = @i + 1 END
CASE式:
SELECT num, CASE WHEN num % 2 = 0 THEN 'even' ELSE 'odd' END AS parity FROM numbers
主な特徴:
CASEはWHEREのように行をフィルタリングするために使用できますか?
いいえ!CASEは異なる値を返しますが、行をフィルタリングすることはありません。CASEを使用してフィルタリングするのは一般的な間違いで、結果は誤ってしまいます。
WHILEとCURSORの違いは何ですか?同じですか?
WHILEは基本的なループで、ユーザーが変数を管理します。CURSORはテーブルのレコードを処理し、行への参照を保持します。CURSORはよりリソース消費が多く、大きなデータに対してはずっと遅くなることがよくあります。
マスプロセッシングにおいては、WHILEループ付きのUPDATEと一つのセットベースUPDATEのどちらが速いですか?
99%のケースでは、一つのセットベースのUPDATE(またはINSERT)が、1件ずつのループよりずっと速くなります(それが「より柔軟」だと見える場合でも)。
-- 不適切なアプローチ DECLARE @id INT = 1 WHILE (@id <= 100000) BEGIN UPDATE t SET flag=1 WHERE id=@id SET @id = @id + 1 END -- 正しい UPDATE t SET flag=1 WHERE id BETWEEN 1 AND 100000
百万件の注文のステータスを更新するために、各IDのためにUPDATEのループを作成しました。処理は約8時間かかりました。半分で失敗した場合は全てを失うことになり、手動で修正する必要がありました。
利点:
欠点:
セットベースのUPDATEを一つの表現に改善しました。実行時間は6分に短縮され、処理は原子的です。
利点:
欠点: