この記事では、カーソルの使用によって引き起こされるパフォーマンスの問題を回避するのに役立つSQLカーソルの使用に代わるいくつかの方法について説明します。
代替案について説明する前に、SQLカーソルの一般的な概念を確認しましょう。
SQLカーソルの概要
SQLカーソルは主に、セットベースの操作が適用できず、オブジェクト全体(テーブルやセットなど)に単一のセットベースの操作を適用するのではなく、データにアクセスして一度に1行ずつ操作を実行する必要がある場合に使用されます。テーブル)。
簡単な定義
SQLカーソルを使用すると、一度に1行ずつデータにアクセスできるため、結果セットを行ごとに直接制御できます。
Microsoftの定義
Microsoftのドキュメントによると、Microsoft SQL Serverステートメントは完全な結果セットを生成しますが、一度に1行ずつ処理するのが最適な場合があります。これは、結果セットでカーソルを開くことで実行できます。
カーソルを使用する5ステップのプロセス
SQLカーソルを使用するプロセスは、一般的に次のように説明できます。
- カーソルを宣言する
- カーソルを開く
- 行を取得
- カーソルを閉じる
- カーソルの割り当てを解除する
重要な注意事項
Vaidehi Pandereによると、カーソルはシステムメモリを占有するポインタであることに注意してください。そうでない場合は、他の重要なプロセス用に予約されます。そのため、カーソルを使用して大きな結果セットをトラバースすることは、正当な理由がない限り、通常は最善のアイデアではありません。
これに関する詳細については、私の記事「特別な目的でSQLカーソルを使用する方法」を参照してください。
SQLカーソルの例
最初に、SQLカーソルを使用してデータベースオブジェクトの名前を1つずつ変更する方法の例を見ていきます。
必要なSQLカーソルを作成するために、サンプルデータベースをセットアップして、それに対してスクリプトを実行できるようにします。
サンプルデータベースのセットアップ(UniversityV3)
次のスクリプトを実行して、UniversityV3サンプルデータベースを作成し、2つのテーブルを設定します。
-- (1) Create UniversityV3 sample database CREATE DATABASE UniversityV3; GO USE UniversityV3 -- (2) Create Course table IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Course') DROP TABLE dbo.Course CREATE TABLE [dbo].[Course] ( [CourseId] INT IDENTITY (1, 1) NOT NULL, [Name] VARCHAR (30) NOT NULL, [Detail] VARCHAR (200) NULL, CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED ([CourseId] ASC) ); -- (3) Create Student table IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Student') DROP TABLE dbo.Student CREATE TABLE [dbo].[Student] ( [StudentId] INT IDENTITY (1, 1) NOT NULL, [Name] VARCHAR (30) NULL, [Course] VARCHAR (30) NULL, [Marks] INT NULL, [ExamDate] DATETIME2 (7) NULL, CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED ([StudentId] ASC) ); -- (4) Populate Course table SET IDENTITY_INSERT [dbo].[Course] ON INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (1, N'DevOps for Databases', N'This is about DevOps for Databases') INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (2, N'Power BI Fundamentals', N'This is about Power BI Fundamentals') INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (3, N'T-SQL Programming', N'About T-SQL Programming') INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (4, N'Tabular Data Modeling', N'This is about Tabular Data Modeling') INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (5, N'Analysis Services Fundamentals', N'This is about Analysis Services Fundamentals') SET IDENTITY_INSERT [dbo].[Course] OFF -- (5) Populate Student table SET IDENTITY_INSERT [dbo].[Student] ON INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (1, N'Asif', N'Database Management System', 80, N'2016-01-01 00:00:00') INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (2, N'Peter', N'Database Management System', 85, N'2016-01-01 00:00:00') INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (3, N'Sam', N'Database Management System', 85, N'2016-01-01 00:00:00') INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (4, N'Adil', N'Database Management System', 85, N'2016-01-01 00:00:00') INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (5, N'Naveed', N'Database Management System', 90, N'2016-01-01 00:00:00') SET IDENTITY_INSERT [dbo].[Student] OFF
テーブルの名前を変更するSQLカーソルを作成する(_Backup)
次に、カーソルを使用して次の仕様を満たすことを検討してください。
- データベース内の既存のすべてのテーブルの名前に「_Backup」を追加する必要があります
- 名前にすでに「_Backup」が含まれているテーブルの名前は変更しないでください
次のコードを実行して、名前に「_Backup」を含むテーブルの名前が再度変更されないようにしながら、各テーブルの名前に「_Backup」を追加して、サンプルデータベース内のすべてのテーブルの名前を変更するSQLカーソルを作成しましょう。
-- Declaring the Student cursor to rename all tables by adding ‘_backup’ to their names and also making sure that all tables that are already named correctly will be skipped: USE UniversityV3 GO DECLARE @TableName VARCHAR(50) -- Existing table name ,@NewTableName VARCHAR(50) -- New table name DECLARE Student_Cursor CURSOR FOR SELECT T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T; OPEN Student_Cursor FETCH NEXT FROM Student_Cursor INTO @TableName WHILE @@FETCH_STATUS = 0 BEGIN IF RIGHT(@TableName,6)<>'Backup' -- If Backup table does not exist then rename the table BEGIN SET @[email protected]+'_Backup' -- Add _Backup to the table’s current name EXEC sp_rename @TableName,@NewTableName -- Rename table as OLD table END ELSE PRINT 'Backup table name already exists: '[email protected] FETCH NEXT FROM Student_Cursor -- Get next row data into cursor and store it in variables INTO @TableName END CLOSE Student_Cursor -- Close cursor locks on the rows DEALLOCATE Student_Cursor -- Release cursor reference
名前変更スクリプトを実行して結果を表示する
次に、SSMS(SQL Server Management Studio)でF5キーを押してスクリプトを実行し、結果を確認します。
SSMSオブジェクトエクスプローラーでテーブルの名前を更新すると、指定どおりにテーブルが正常に変更されたことが明確に示されます。
もう一度F5キーを押してスクリプトを実行し、結果を確認してみましょう。
_BackupネーミングをリセットするためのSQLカーソルの作成
また、SQLカーソルを使用して、変更したばかりのテーブルの名前を最初のテーブルに戻すスクリプトを作成する必要があります。これを行うには、名前から「_Backup」を削除します。
以下のスクリプトでそれを実行できます:
-- Declare the Student cursor to reset tables names _backup to their original forms by removing ‘_backup’ USE UniversityV3 GO DECLARE @TableName VARCHAR(50) -- Existing table name ,@NewTableName VARCHAR(50) -- New table name DECLARE Student_Cursor CURSOR FOR SELECT T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T; OPEN Student_Cursor FETCH NEXT FROM Student_Cursor INTO @TableName WHILE @@FETCH_STATUS = 0 BEGIN IF RIGHT(@TableName,6)='Backup' -- If Backup table name exists then reset (rename) it BEGIN SET @NewTableName=SUBSTRING(@TableName,1,LEN(@TableName)-7) -- Remove _Backup from the table name EXEC sp_rename @TableName,@NewTableName -- Rename table END ELSE PRINT 'Backup table name already reset: '[email protected] FETCH NEXT FROM Student_Cursor – Get the data of the next row into cursor and store it in variables INTO @TableName END CLOSE Student_Cursor -- Close cursor locks on the rows DEALLOCATE Student_Cursor -- Release cursor reference
リセットスクリプトを実行して結果を表示する
スクリプトを実行すると、テーブル名が正常にリセットされたことがわかります。
これらは、要件の性質上、SQLカーソルの使用を回避することが難しいいくつかのシナリオの例です。ただし、別のアプローチを見つけることは可能です。
SQLカーソルの代替
SQLカーソルには2つの最も一般的な選択肢があるので、それぞれについて詳しく見ていきましょう。
代替案1:テーブル変数
これらの選択肢の1つは、テーブル変数です。
テーブル変数は、テーブルと同様に、複数の結果を格納できますが、いくつかの制限があります。 Microsoftのドキュメントによると、テーブル変数は、後で処理するために結果セットを格納するために使用される特別なデータ型です。
ただし、テーブル変数は小さなデータセットで使用するのが最適であることに注意してください。
テーブル変数はローカル変数のように機能し、スコープから外れると自動的にクリーンアップされるため、小規模なクエリには非常に効率的です。
テーブル変数戦略:
次の手順に従って、SQLカーソルの代わりにテーブル変数を使用して、データベースのすべてのテーブルの名前を変更します。
- テーブル変数を宣言する
- テーブルの名前とIDを宣言したテーブル変数に格納します
- カウンターを1に設定し、テーブル変数からレコードの総数を取得します
- カウンターがレコードの総数以下である限り、「while」ループを使用します
- 「while」ループ内で、テーブルの名前がまだ変更されていない限り、テーブルの名前を1つずつ変更し、各テーブルのカウンターを増やします
テーブル変数コード:
次のSQLスクリプトを実行して、テーブル変数を作成および使用してテーブルの名前を変更します。
-- Declare Student Table Variable to rename all tables by adding ‘_backup’ t their name and also making sure that already renamed tables are skipped USE UniversityV3 GO DECLARE @TableName VARCHAR(50) -- Existing table name ,@NewTableName VARCHAR(50) -- New table name DECLARE @StudentTableVar TABLE -- Declaring a table variable to store tables names ( TableId INT, TableName VARCHAR(40)) INSERT INTO @StudentTableVar -- insert tables names into the table variable SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T DECLARE @TotalRows INT=(SELECT COUNT(*) FROM @StudentTableVar),@i INT=1 -- Get total rows and set counter to 1 WHILE @i<[email protected] -- begin as long as i (counter) is less than or equal to the total number of records BEGIN -- ‘While’ loop begins here SELECT @TableName=TableName from @StudentTableVar WHERE [email protected] IF RIGHT(@TableName,6)<>'Backup' -- If a Backup table does not exist, then rename the table BEGIN SET @[email protected]+'_Backup' -- Add _Backup to the table’s current name EXEC sp_rename @TableName,@NewTableName -- Rename the table as OLD table END ELSE PRINT 'Backup table name already exists: '[email protected] SET @[email protected]+1 END -- 'While' loop ends here
スクリプトを実行して結果を表示する
それでは、スクリプトを実行して結果を確認しましょう。
代替案2:一時的なテーブル
SQLカーソルの代わりに一時テーブルを使用して、結果セットを一度に1行ずつ繰り返すこともできます。
一時テーブルは長い間使用されており、大規模なデータセットのカーソルを置き換えるための優れた方法を提供します。
テーブル変数と同様に、一時テーブルは結果セットを保持できるため、「while」ループなどの反復アルゴリズムで処理することにより、必要な操作を実行できます。
一時的なテーブル戦略:
次の手順に従って、一時テーブルを使用してサンプルデータベース内のすべてのテーブルの名前を変更します。
- 一時テーブルを宣言する
- 宣言した一時テーブルにテーブルの名前とIDを保存します
- カウンターを1に設定し、一時テーブルからレコードの総数を取得します
- カウンターがレコードの総数以下である限り、「while」ループを使用します
- 「while」ループ内で、テーブルの名前がまだ変更されていない限り、テーブルの名前を1つずつ変更し、各テーブルのカウンターを増やします
テーブルをリセット
テーブルの名前の末尾から「_Backup」を削除してテーブルの名前を初期形式にリセットする必要があるため、テーブルの名前を変更する別の方法を適用できるように、上記で作成して使用したリセットスクリプトを再実行してください。
一時テーブルコード:
次のSQLスクリプトを実行して一時テーブルを作成および使用して、データベース内のすべてのテーブルの名前を変更します。
-- Declare the Student Temporary Table to rename all tables by adding ‘_backup’ to their names while also making sure that already renamed tables are skipped USE UniversityV3 GO DECLARE @TableName VARCHAR(50) -- Existing table name ,@NewTableName VARCHAR(50) -- New table name CREATE TABLE #Student -- Declaring a temporary table ( TableId INT, TableName VARCHAR(40) ) INSERT INTO #Student -- insert tables names into the temporary table SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T DECLARE @TotalRows INT=(SELECT COUNT(*) FROM #Student),@i INT=1 -- Get the total amount of rows and set the counter to 1 WHILE @i<[email protected] -- begin as long as i (counter) is less than or equal to the total number of records BEGIN -- ‘While’ loop begins here SELECT @TableName=TableName from #Student WHERE [email protected] IF RIGHT(@TableName,6)<>'Backup' -- If a Backup table does not exist, then rename the table BEGIN SET @[email protected]+'_Backup' -- Add ‘_Backup’ to the table’s current name EXEC sp_rename @TableName,@NewTableName -- Rename the table as OLD table END ELSE PRINT 'Backup table name already exists: '[email protected] SET @[email protected]+1 END -- While loop ends here DROP TABLE #Student
スクリプトを実行して出力を確認する
それでは、スクリプトを実行して結果を表示しましょう。
やるべきこと
テーブル変数や一時テーブルの使用など、SQLカーソルの代替手段に慣れてきたので、この知識を実際に適用することに慣れるために、次のことを試してください。
- サンプルデータベース内のすべてのテーブルのインデックスを作成して名前を変更します。最初はカーソルを使用し、次に別の方法(テーブル変数と一時テーブル)を使用します。
- 別の方法(一時テーブルとテーブル変数)を使用して、この記事のテーブルの名前を最初の名前に戻します
- また、私の記事の最初の例を参照して、SQLカーソルを特別な目的で使用し、テーブルに多数の行を入力して、クエリの統計と時間を測定し、基本的なカーソルの方法を代替方法と比較することもできます。