sql >> データベース >  >> RDS >> Database

パフォーマンスの問題を回避するための代替によるSQLカーソルの置き換え

    この記事では、カーソルの使用によって引き起こされるパフォーマンスの問題を回避するのに役立つSQLカーソルの使用に代わるいくつかの方法について説明します。

    代替案について説明する前に、SQLカーソルの一般的な概念を確認しましょう。

    SQLカーソルの概要

    SQLカーソルは主に、セットベースの操作が適用できず、オブジェクト全体(テーブルやセットなど)に単一のセットベースの操作を適用するのではなく、データにアクセスして一度に1行ずつ操作を実行する必要がある場合に使用されます。テーブル)。

    簡単な定義

    SQLカーソルを使用すると、一度に1行ずつデータにアクセスできるため、結果セットを行ごとに直接制御できます。

    Microsoftの定義

    Microsoftのドキュメントによると、Microsoft SQL Serverステートメントは完全な結果セットを生成しますが、一度に1行ずつ処理するのが最適な場合があります。これは、結果セットでカーソルを開くことで実行できます。

    カーソルを使用する5ステップのプロセス

    SQLカーソルを使用するプロセスは、一般的に次のように説明できます。

    1. カーソルを宣言する
    2. カーソルを開く
    3. 行を取得
    4. カーソルを閉じる
    5. カーソルの割り当てを解除する

    重要な注意事項

    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)

    次に、カーソルを使用して次の仕様を満たすことを検討してください。

    1. データベース内の既存のすべてのテーブルの名前に「_Backup」を追加する必要があります
    2. 名前にすでに「_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カーソルの代わりにテーブル変数を使用して、データベースのすべてのテーブルの名前を変更します。

    1. テーブル変数を宣言する
    2. テーブルの名前とIDを宣言したテーブル変数に格納します
    3. カウンターを1に設定し、テーブル変数からレコードの総数を取得します
    4. カウンターがレコードの総数以下である限り、「while」ループを使用します
    5. 「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」ループなどの反復アルゴリズムで処理することにより、必要な操作を実行できます。

    一時的なテーブル戦略:

    次の手順に従って、一時テーブルを使用してサンプルデータベース内のすべてのテーブルの名前を変更します。

    1. 一時テーブルを宣言する
    2. 宣言した一時テーブルにテーブルの名前とIDを保存します
    3. カウンターを1に設定し、一時テーブルからレコードの総数を取得します
    4. カウンターがレコードの総数以下である限り、「while」ループを使用します
    5. 「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カーソルの代替手段に慣れてきたので、この知識を実際に適用することに慣れるために、次のことを試してください。

    1. サンプルデータベース内のすべてのテーブルのインデックスを作成して名前を変更します。最初はカーソルを使用し、次に別の方法(テーブル変数と一時テーブル)を使用します。
    2. 別の方法(一時テーブルとテーブル変数)を使用して、この記事のテーブルの名前を最初の名前に戻します
    3. また、私の記事の最初の例を参照して、SQLカーソルを特別な目的で使用し、テーブルに多数の行を入力して、クエリの統計と時間を測定し、基本的なカーソルの方法を代替方法と比較することもできます。

    1. SET NULL:SQLcl / SQL*PlusでNULL値が発生した場合に返される文字列を指定します

    2. 統計の自動更新がクエリのパフォーマンスにどのように影響するか

    3. ORA-00947タイプをグローバルに宣言する際に値が不足しています

    4. 非同期I/Oを多用するコードは、非非同期よりも実行速度が遅いのはなぜですか?