【サンプル満載】SqlServer ストアド プロシージャ自由自在(stored procedure)

SQL入門
この記事は約11分で読めます。

SqlServer、Azure SQL Database、Azure Synapse 上でストアドプロシージャを作成する場合、Transact-SQL と呼ばれる言語を使用します。

今回は、Transact-SQLを使ったストアドプロシージャの書き方について、サンプルプログラムを交えて解説します。

Transact-SQLは、他のデータベースと比べて少し特殊な書き方をするため、OracleやPostgreSqlの経験者にとっても役立つ記事になっています。

これから Transact-SQL を始めようという方は、是非ご一読ください。

ストアドプロシージャの概要

ストアドプロシージャとは、あらかじめデータベース内に登録しておき、必要に応じて呼び出して使うプログラムの事です。

通常は、アプリケーションからデータベースに対してSQLを実行する場合、実行したいSQLの数だけデータベースとの通信が発生します。

実行したいSQLをあらかじめデータベースに登録しておき、アプリケーションから呼び出すことで、データベースとの通信が1回で済みます。

ストアドプロシージャの概要説明図

数件程度のSQL実行であれば問題ありませんが、数千~数万件のSQLを発行するような場合には威力を発揮します。

また、ストアドプロシージャのメリットはこれ以外にも、

  • 複雑な処理を共通化/部品化できる
  • あらかじめコンパイルされるため、処理が速くなる

などのメリットもあります。

ストアドプロシージャの種類

ストアドプロシージャには、「結果を返す」ものと、「結果を返さない」ものが存在します。また、「結果を返す」場合において、「複数の結果を返すもの」と「単一の結果を返すもの」が存在します。

「結果を返すもの」はストアドプロファンクション(stored function)と呼んでおり、「結果を返さない」ものはストアドプロシージャ(stored procedure)と呼んで区別します。

ストアドプロシージャの概要説明図2

最初に知っておきたい記述上のルール

SQL Server や Azure SQL Databaseでストアドプロシージャ(以降はプロシージャと呼びます)を作成する場合、Transact-SQL と呼ばれる文法を使用します。

Transact-SQL は、Oracle や PostgreSQLなど、他のリレーショナルデータベースとは少し違った書き方をするため、最初にポイントを整理しておきます。

  • 引数、変数は 必ず@から始める
  • 変数に配列は使えない
  • ループ処理はWHILEのみで、FOR文は存在しない
  • プロシージャから任意の値を返すことが出来る
  • ファンクションの中にSQLは記述できない
  • 行末はセミコロンをつけることが推奨されているが、無くても動く
  • カラム名に特殊文字や予約語を含む場合、[ ] でくくる

上記のことを軽く念頭に置いて、以降の記事を読み進めてください。

事前準備

本記事では次のテーブルがあると仮定して説明しています。掲載しているソースコードの動作検証を行う場合、あらかじめ下記のSQLを実行し、テーブルを作成しておいて下さい。

基本構文

では、Tracsact-SQLの基本構文について解説したいと思います。

プロシージャの基本フォーマット

プロシージャの基本的なフォーマットは次の通りです。

ストアドプロシージャ構文の図解

プロシージャ内でSELECT文で値を取得し、変数に格納して使うケースが多々ありますが、Transact-SQLの場合はSELECT内で直接変数に値を代入することが可能です。

OracleやPostgreSQLでは、SELECTの値を取得する場合に INTO 変数1,・・・ を記述する必要があったので、Transact-SQLにおけるこの記述は、他のデータベースから比べると斬新です。

ちなみに、引数にはデフォルト値を指定することが可能で、デフォルト値を指定した引数は省略可能になります。

以上の内容を踏まえたサンプルは次のようになります。

呼び出し方法は2通りの記述方法があります。1つ目はプロシージャの引数定義に順番を合わせる方法、もう1つは引数名を指定する方法です。引数名を指定すれば、引数の順番は無視できます。

ストアドプロシージャの呼び出し方
ストアドプロシージャ実行結果

値を返すストアドの基本フォーマット

戻り値の返し方は2つあり、1つは引数に OUT 句を指定する方法、もう1つはプロシージャの最後に SELECT を記述する方法です。

引数で返す場合は、単に引数に値を代入するだけでOKです。SELECTを使う場合は、返したい値を続けて記述するだけですが、返す個数が1個の場合と2個以上の場合では、受け取り方が違ってきます。

尚、引数を使う方法とSELECTを使う方法は、同時に使うことが可能です。

ストアドプロシージャ構文の図解

引数とSELECTを使ったサンプル(SELECTで値を1個だけ返す場合)

下記は引数とSELECTを使って呼び出し元に値を返すサンプルです。

SELECT で返す値が1個の場合は、変数に直接代入できます。引数で値を受け取るためには、変数の後に OUT を記述します。

SELECTで値を2個以上返す場合

下記のサンプルは、前述のサンプルでは引数@Total で呼び出し元に返していた値を、SELECTで返すよう修正したものです。

SELECT で値を2個以上返す場合は、単純に変数で受け取ることが出来ません。

ストアドプロシージャ実行結果

この場合は TABLE形式のデータタイプ(データ型)を作成し、 INSERT INTO 変数名~ と記述する必要があります。

ストアドプロシージャ構文の図解

以下の通り CREATE TYPE で TABLE 型の変数を定義し、INSERT INTO 変数名 EXEC ストアド名 ~ を実行することで、戻り値を変数に格納できます。

TABLE 型 は名前が示す通り表形式のデータを保持するデータ型です。SELECTで2個の値を指定するということは、「2個のカラムを持つテーブルから、1行だけ値が返ってきた」という扱いになります。

TALBE型から値を取り出すときは、後述するFETCH文を使います。

次は、SELECTを使ってテーブル丸ごと返すプロシージャを考えてみます。

例えば、複数のテーブルを結合し、かつ任意の項目を加工してテーブル形式で返す場合は、次の様に記述できます。

プロシージャの中でCreate Tableにより一時テーブルを作成すれば、 SELECT 文でその中身を丸ごと返すことも可能です。

一時テーブルの先頭に # を付けていますが、これを付けるとセッション内でのみ有効な(セッションが終わると自動で削除される)一時テーブルを作成することができます。

通常はBIGIN~ENDで明示的なトランザクション制御が掛かるので、DROP TABLE #TEMPをわざわざ記述しなくても勝手に消えてくれますが、念のため最後に実行しています。

理由は、#TEMPはセッション中はずっと存在し続けるため、別のストアド内で同じ名前(#TEMP)でCREATEするとエラーになるからです。単純なテーブル名で作成する場合は、使い終わった時点でDROPしておく方が安心です。

RETURNで整数を返す

OracleやPostgreSQLのプロシージャでは、RETURN文を使って呼び出し元に値を返せませんでしたが、Transact-SQLでは値を返すことが可能です。

ただし、返せる値は整数値が1個だけです。浮動小数点を記述することは可能ですが、INT型にキャストされます。

この方法は、プロシージャの実行結果(成功、失敗、エラーコードなど)を呼び出し側に返したいときによく利用されます。

ストアドプロシージャ構文の図解

ストアドプロシージャの呼び出し方

SELECT の直後に RETURN を記述した場合、SELECTの値が優先されます。

RETURNの後にSELECTを記述した場合、RETURNの値が返され、SELECTは実行されません。

制御構文

ここからは、制御構文について解説していきます。

条件分岐

条件分岐は次のように記述します。

ストアドプロシージャ構文の図解

下記は引数で渡された商品IDの値によって、文字列をPRINTするサンプルです。

実行すると次の様になります。

ストアドプロシージャ実行結果

ループ処理

FOR文が用意されていないため全てのループはWHILEを使うことになります。

下記は引数で指定された件数だけループして、SalesテーブルにダミーデータをINSERTするサンプルです。

実行した結果、次の様になります。

ストアドプロシージャ実行結果

FETCH 処理

テーブル(TABLE型含む)から値を順番に取り出すには FETCH 文を使います。

事前にカーソル型の変数を用意し、そこにSELECT文を登録しておきます。そして、OPEN文でカーソルを開き、FETCH NEXT~でレコードを順番に取り出します。

@@FETCH_STATUS は取り出すレコードが無くなると0以外になるため、ループ条件に使います。@が2つ連続しているのはTransact-SQL の予約変数だからです。

処理が終わったら、CLOSEでカーソルを閉じ、DEALLOCATEでカーソルが使っていたメモリを解放します。

ストアドプロシージャ構文の図解

下記はSalseテーブルから1レコードづつ取り出し、PRINT文でコンソールに出力するサンプルです。

実行結果は次のようになります。

ストアドプロシージャ実行結果

トランザクションと例外処理

例外を補足し、ロールバックを実行するには次のように記述します。

ストアドプロシージャ構文の図解

正常終了すると自動でコミットされますので、明示的にCOMMITを書く必要はありません。

XACT_STATE() 関数はトランザクションの状態を取得する関数で、トランザクションが完了している場合は0が返されます。従って、0以外の場合、ロールバックするようにします。

以下は、例外処理とロールバックを使ったサンプルです。

動的SQL

動的SQLとは、SQL文を文字列として定義したもので、EXEC文によりSQLとして実行できます。

EXCELはプロシージャを呼ぶときにも使いましたが、動的SQLを実行する場合は ( ) で括る必要があります。

ストアドプロシージャ構文の図解

下記は、引数で指定された、カンマ区切りのカラム名を使ってSalesテーブルから値を取得するサンプルです。

結果は次の通りです。第2引数で指定された ProductID と ProductName のみが抽出されています。

ストアドプロシージャ実行結果

今回の様に短い動的SQLであれば、EXECの中に直接書くことも可能です。

ファンクションについて

Transact-SQL でいうファンクションは、OracleやPostgreSQLなどで使われるファンクションと少々異なります。

一般的なデータベースでは、値を返さないものをプロシージャ、値を返すものをファンクションと呼んで区別しますが、値を返す部分以外は両者に違いはありません。

一方、Transact-SQL のプロシージャは、プロシージャとファンクションを併せ持った存在です。

では、Transact-SQL で用意されているプロシージャは何かというと、「単純な処理を関数化したもの」という位置づけで、処理にSQL文(SELECT、INSERT、DELETE、UPDATE、MERGE)が記述できません。

ストアドプロシージャ構文の図解

従って、下記のような短い処理を関数化する用途で使用します。

呼び出し方は、組み込み関数であるlen()やreplace()と同じように直接変数で受け取ったり、SELECTの中で使うことが可能です。

ストアドプロシージャ実行結果

まとめ

今回はマイクロソフト系のデータベースエンジン(SQL Sever、SQL Database、Azure Synapse)で利用可能な Transact-SQL について、ストアドとファンクションの書き方について解説しました。

OracleやPostgreSQLなど、他のデータベースでは戻り値を返さないプロシージャと、戻り値を返すプロシージャが区別されており、後者はファンクションと呼ばれています。

一方、Transact-SQLのプロシージャは、プロシージャとファンクションの両方を兼ね備えた存在です。

そして、Transact-SQLで用意されているファンクションは、組み込み関数である len()やreplace()のような単一機能の自作関数を作るために用意されており、関数内部でSQL文が記述できません。

これ以外にも、FORが用意されていない、配列変数が使えない、変数や引数は必ず@から始まる、SELECTの中で直接イコールで変数に代入できるなど、他のデータベースとは少し異なる点が特徴です。

他のデータベース経験者であれば、最初は戸惑うかもしれませんが、慣れると結構便利で書きやすいと思います。

もしTransact-SQLで何か作る機会があれば、是非この記事を参考にしてください。

タイトルとURLをコピーしました