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

当ページのリンクには広告が含まれています。

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を実行し、テーブルを作成しておいて下さい。

プロシージャの書き方

最初に、本記事の注意事項について述べさせていただきます。

IT業界においては、ストアドプロシージャの呼称が人によって異なり、ストアド、ストプロ、プロシージャなどの呼び方をします。

本記事ではプロシージャという呼称で統一していますが、もし上記の記述があった場合は全て同じものであるとご理解ください。

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

値を返さない場合

値を返さないプロシージャの基本フォーマットは次の通りです。下記の例では引数を2つ指定していますが、引数が不要な場合はもちろん省略が可能です。

ここで他のデータベースと異なる点について簡単に触れておきたいと思います。

  • 引数名、変数名は必ず@から始める必要があります。
  • OracleやPostgreSQLでは引数部分は()で括る必要がありましたが、Transact-SQLの場合は不要です。逆に付けるとエラーになります。
  • プロシージャ内でSELECT文で値を取得し、変数に格納して使うケースが多々ありますが、Transact-SQLの場合はSELECT内で直接変数に値を代入することが可能です。

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

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

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

プロシージャの実行方法

プロシージャの実行は EXEC を使います。具体的にはEXECの後にスキーマ名.ストアド名を指定します。

この時、引数の渡し方には2通りがあり、1つはプロシージャの引数の定義順に値を渡す方法、もう1つは「引数名=値」の形式で指定する方法です。

「引数名=値」の方法であれば、引数の順番は定義順である必要はありません。

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

任意の値を返したい場合

プロシージャから任意の値を返す場合は、受取り専用の引数を定義して使います。
Oracle や PostgreSQL ではストアドファンクションを作成し、その戻り値を受けとりますが、SQL Serverの場合は戻り値ではなく引数を使うところが大きな違いです。

具体的には、プロシージャの引数として、値を受け取る変数を引数に指定し、プロシージャ内でその変数に値を代入します。その際、値を受け取る変数であることを明示するため、変数名の後にOUT句(OUT又はOUTPUT)を記述します。

そして、プロシージャの中からOUT句を指定した変数(=OUT変数)に返したい値を代入します。この時、代入の仕方にはSETを使う方法とSELECTを使う方法の2通りがあります。

方法1.SET文を使って値を返す

下記はSETを使って呼び出し元に値を返す場合の書き方です。OUTの引数それぞれにSETを使って値を代入しています。

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

方法2.SELECT文を使って値を返す

下記はSELECTを使って呼び出し元に値を返す場合の書き方です。引数で指定したOUT変数には、SELECTで指定した順番に代入されていきます。

任意の値を返すプロシージャの実行方法と値の受け取り方

値を返すプロシージャであっても EXEC を使います。先ほどと異なるのは、受け取るためのOUT変数を記述する点です。

表形式で値を返したい場合

表形式とは、SELECT * FROM テーブル名 で取得できる行列データのことです。これを使うことであたかも物理的なテーブルに対してSELECT文を実行したのと同じ結果が得られます。

下記のサンプルは、プロシージャの引数をOUT変数 で呼び出し元に返していた値を、表形式で返すよう修正したものです。

このサンプルではSELECT文が1回実行されるだけなので、2カラム1レコードのデータが返されることになります。

先ほど紹介したOUT変数を使った場合との相違点は以下の通りです。

  • プロシージャの引数としてOUT変数は記述しない
  • SELECTを連続実行することで、複数のレコードとして返すことが出来る(SELECT文と同じ)

表形式で値を返すプロシージャの実行方法

実行方法も今までのプロシージャと同じですが、値の受け取り方が異なります。

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

表形式で返された値の受け取り方

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

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

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

では、複数レコードを受け取りたい場合はどうすればよいでしょう?

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

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

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

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

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

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

RETURNで整数値を返す

OracleやPostgreSQLのプロシージャでは、RETURN文を使って呼び出し元に値を返すことができません。
一方、SQLServer(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をコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次