SQL Serverでカーソルを使う時の備忘録
2022-07-06
azblob://2022/11/11/eyecatch/2022-07-06-sql-server-cursor-fetch-000.png

こんにちは、あおいです。
今年の夏も猛暑ですが、水分補給をしっかりして熱中症対策をしましょう、、、マスクが暑すぎる、、、



さて、T-SQLのカーソルとは、SELECT 文で抽出されたレコードを1行ずつフェッチ(取得)して、複雑な処理をしたい場合に有効です。

ただし、場合によっては処理のパフォーマンスを落ちる可能性もあるので、カーソルの使用には検討の必要があります…



今回はSQL Serverでカーソルの使い方を備忘録として記事に残します。

いつもどうやって書くんだっけ?となって忘れちゃうので(忘れるな)、この記事見れば大丈夫(のはず)。



OldCustomerテーブルのデータをNewCustomerテーブルにインサートする、という超シンプルなロジックでカーソル処理を試してみます。

まず、以下のスクリプトを実行します。

DROP TABLE IF EXISTS OldCustomer
DROP TABLE IF EXISTS NewCustomer

CREATE TABLE OldCustomer(
	CustomerID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
	Name NVARCHAR(20),
	Job NVARCHAR(20),
	InvestmentHistory INT
)

CREATE TABLE NewCustomer(
	CustomerID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
	Name NVARCHAR(20),
	Job NVARCHAR(20),
	InvestmentHistory INT
)

INSERT INTO OldCustomer
	(Name, Job, InvestmentHistory)
VALUES
(N'佐藤一郎', N'公務員', 1),
(N'鈴木二郎', N'会社員', 3),
(N'高橋三郎', N'大学生', 0),
(N'田中四郎', N'エンジニア', 5),
(N'伊藤五郎', N'経営者', 20)




①カーソルで使用する変数を宣言

DECLARE @Name NVARCHAR(20),
		@Job  NVARCHAR(20),
		@InvestmentHistory INT




②カーソルを宣言する

DECLARE cs CURSOR 
	LOCAL FAST_FORWARD
FOR
	SELECT Name, Job, InvestmentHistory
	FROM Customer




③カーソルを開く

OPEN cs




④ループでレコードを1行ずつフェッチ(取得)して処理

FETCH NEXT FROM cs 
INTO @Name, @Job, @InvestmentHistory

WHILE @@FETCH_STATUS = 0
		BEGIN
	
			INSERT INTO NewCustomer
			VALUES
			(@Name, @Job, @InvestmentHistory)

			FETCH NEXT FROM cs
			INTO @Name, @Job, @InvestmentHistory

		END




⑤カーソルを閉じる

CLOSE cs




⑥リソースを開放する

DEALLOCATE cs





また、トランザクション制御と例外処理も頻繁に使う大事な操作なので、セットとして覚えておきましょう。

トランザクション制御の基本構文は以下の通りです。

BEGIN TRANSACTION -- トランザクションの開始
[一連の処理]
COMMIT TRANSACTION -- トランザクションを終了して、変更を確定する
ROLLBACK TRANSACTION -- トランザクションを終了して、変更を取り消す



単純に、ロールバックでトランザクションを打ち消したいだけの場合、XACT_ABORTをONにすると良いです。XACT_ABORT を ON にすると、エラーが起こるとトランザクションの開始までロールバックしてくれるので、ROLLBACK TRANSACTION を使う必要はありません。

SET XACT_ABORT ON
BEGIN TRANSACTION
[一連の処理]
COMMIT TRANSACTION





例外処理の基本構文は以下の通りです。

BEGIN TRY
[例外が発生しうる処理]
END TRY

BEGIN CATCH
[例外発生時の処理]
END CATCH





全てを踏まえて記述すると、以下の通りになります。

-- カーソルで使用する変数を宣言
DECLARE @Name NVARCHAR(20),
		@Job  NVARCHAR(20),
		@InvestmentHistory INT

-- カーソルを宣言する
DECLARE cs CURSOR
  -- 順次にデータを取得するだけの場合、FAST_FOWARDオプションを付けるとパフォーマンス向上
	LOCAL FAST_FORWARD
FOR
	SELECT Name, Job, InvestmentHistory
	FROM Customer

-- カーソルを開く
OPEN cs

-- ループでレコードを1行ずつ取得して処理
FETCH NEXT FROM cs 
INTO @Name, @Job, @InvestmentHistory

BEGIN TRY
	BEGIN TRANSACTION -- トランザクションの開始
		WHILE @@FETCH_STATUS = 0
		BEGIN
	
			INSERT INTO NewCustomer
			VALUES
			(@Name, @Job, @InvestmentHistory)

			-- 次の行を取得する
			FETCH NEXT FROM cs
			INTO @Name, @Job, @InvestmentHistory

		END
	COMMIT TRANSACTION --トランザクションを確定
END TRY

-- 例外処理
BEGIN CATCH
	ROLLBACK TRANSACTION  -- トランザクションの打ち消し
	PRINT ERROR_MESSAGE() -- エラー内容を出力
END CATCH

-- カーソルを閉じる
CLOSE cs
-- リソースを解放する
DEALLOCATE cs




今回はSQL Serverでカーソルを使う時の備忘録をただ書き記しました。備忘録なのでお許しを。

参考記事
カーソル(Transact-SQL) | Microsoft Docs
トランザクション(Transact-SQL) | Microsoft Docs
TRY...CATCH(Transact-SQL) | Microsoft Docs

【補足】

今回の場合、OldCustomerテーブルのデータをNewCustomerテーブルにそのまま挿入している処理なので、カーソルを使わずに INSERT INTO ... SELECT の記述で同様の処理が可能です。
ただ、INSERT INTO ... SELECT での記述はインサート先のテーブルが既に存在していることが前提です。

INSERT INTO NewCustomer(
	Name,
	Job,
	InvestmentHistory
)
SELECT Name,
	Job,
	InvestmentHistory
FROM OldCustomer




もし、インサート対象のテーブルを用意するのが面倒であれば、SELECT INTO ... FROM で処理が可能です。
ただ、SELECT INTO ... FROMでの記述はインサート先のテーブルが存在していないことが前提です。

SELECT Name,
	   Job,
	   InvestmentHistory
INTO NewCutomer
FROM OldCustomer