Chapter 13. SQLの拡張: 関数

Table of Contents
13.1. 問い合わせ言語 (SQL) 関数
13.2. 手続型言語関数
13.3. 内部関数
13.4. コンパイルされたC言語関数
13.5. 関数のオーバーロード

新しい型の定義を行うことは、結局のところ、その関数の動作の 定義を行うということになります。従って、新しい型を定義せずに 新しい関数を定義する事は可能ですが、その逆はできません。 そこで、この章では新しい型を定義する前に新しい関数を Postgresに追加する方法を 記します。

PostgresSQLでは、 下記の3つの関数を提供しています。

すべての関数は、基本型、複合型、または変数(引数)の組み合せを 受け付けることが可能です。また、すべての関数は基本型、または 複合型を返すことが可能です。SQL関数の定義の方法は簡単ですので、 そちらから説明します。この章の例はfuncs.sqlfuncs.cでも参照することができます。

13.1. 問い合わせ言語 (SQL) 関数

SQL関数は、任意なSQL問い合わせリストを実行し、そのリストの 最終結果を返します。SQL関数は、一般的には組(set)を返します。 setofで返り型が設定されていない場合は、 問い合わせの最終結果の任意の要素が返されます。

ASの後に続くSQL関数の本文は、問い合わせのリストで構成され、 それぞれの問い合わせがセミコロンで区切られ、なおかつ シングルクォーテーションで囲われたものである必要があります。 問い合わせで使用されたクォーテーションマークは その前にバックスラッシュを入れてエスケープさせる必要があります。

SQL関数の引数は問い合わせ内で$nを用いて表すことができます。 つまり、$1は第1引数を示し、$2は第2引数...となります. 引数が複雑な場合、"$1.emp"のようなdot 表記を呼び出された関数で使用することができます。

13.1.1. 例

簡単なSQL関数の例として、下記の銀行口座のからお金を 引き出す例をご覧下さい。

CREATE FUNCTION tp1 (int4, float8) 
    RETURNS int4
    AS 'UPDATE bank 
        SET balance = bank.balance - $2
        WHERE bank.acctountno = $1;
        SELECT 1;'
LANGUAGE 'sql';
     
ユーザは下記の関数を使って、口座番号17から100ドルを引き出すことが 可能です。
SELECT tp1( 17,100.0);
     

下記の関数はEMP型の引数を1つを持った関数の例です。 これは引数は1つですが、複数の結果を返すものです。

CREATE FUNCTION hobbies (EMP) RETURNS SETOF hobbies
    AS 'SELECT hobbies.* FROM hobbies
        WHERE $1.name = hobbies.person'
    LANGUAGE 'sql';
     

13.1.2. 基本型を使ったSQL関数

最も簡単なSQL関数は引数がなく、 返す型がint4などの基本型の場合です。

CREATE FUNCTION one() 
    RETURNS int4
    AS 'SELECT 1 as RESULT;' 
    LANGUAGE 'sql';

SELECT one() AS answer;

+-------+
|answer |
+-------+
|1      |
+-------+
     

上記の関数内で、RESULTという名前を関数の結果の列名として 指定しましたが、関数の外ではその指定した名前は 見えないので、列名がanswerとなっていることに ご注意下さい。

基本型を引数として持つSQL関数を定義することも 同じように簡単です。下記の例において、関数内で引数を 参照するために$1と$2をどのように使用しているかに注意して下さい。

CREATE FUNCTION add_em(int4, int4) 
    RETURNS int4
    AS 'SELECT $1 + $2;' 
    LANGUAGE 'sql';

SELECT add_em(1, 2) AS answer;

+-------+
|answer |
+-------+
|3      |
+-------+
     

13.1.3. 複合型を使ったSQL関数

EMPなどのような複合型の引数を持つ関数を指定する時には、 使用する引数を上記の例では単に$1、$2としていましたが、 それに加えて引数の属性も指定する必要があります。 例えば、下記のような給料が倍になったらいくらになるのかを 計算するdouble_salary関数をご覧下さい。

CREATE FUNCTION double_salary(EMP) 
    RETURNS int4
    AS 'SELECT $1.salary * 2 AS salary;' 
    LANGUAGE 'sql';

SELECT name, double_salary(EMP) AS dream
    FROM EMP
    WHERE EMP.cubicle ~= point '(2,1)';


+-----+-------+
|name | dream |
+-----+-------+
|Sam  | 2400  |
+-----+-------+
     

$1.salaryという文法の使用にご注意下さい。複合型を返す関数の話に 進む前に、まず、属性を引き出すための関数記述方法を 紹介しなくてはいけません。簡単にそれを説明すると、 通常は属性(テーブル)とクラス名.属性(table.attribute)の どちらの記述方法でも使用できます。

--
-- this is the same as:
--  SELECT EMP.name AS youngster FROM EMP WHERE EMP.age < 30
--
SELECT name(EMP) AS youngster
    FROM EMP
    WHERE age(EMP) < 30;

+----------+
|youngster |
+----------+
|Sam       |
+----------+
     

ご覧のように、これは常に成り立たないことがお分かり頂けると 思います。この関数記述方法は、1つの行を返す関数を使いたい時には 重要になります。それを行うには、 関数内で1つの行すべてを 属性毎に組み立てます。下記は1つのEMP行を返す関数の例です。

CREATE FUNCTION new_emp() 
    RETURNS EMP
    AS 'SELECT text ''None'' AS name,
        1000 AS salary,
        25 AS age,
        point ''(2,2)'' AS cubicle'
    LANGUAGE 'sql';
     

この例では、各属性を定数を用いて指定していますが、 これらの定数を演算や式に置き換える事も可能です。 このような関数の定義はコツが必要になります。 より重要な注意点のうちの数点を下記に示します。

  • 対象リストの順番はCREATE TABLE行(または .* クエリーを実行した時)に 書かれている属性と正確に同じでである必要があります。

  • 型キャストを行なう必要があります。行わない場合、下記のような エラーメッセージが表示されます。

    	 ERROR:  function declared to return emp returns varchar instead of text at column 1
    	 
    	

  • 行を返す関数を呼び出す時、行全体を取り出すことはできません。 その行から属性を引き出すか、別の関数にそのまま渡すかを する必要があります。

    SELECT name(new_emp()) AS nobody;
    
    +-------+
    |nobody |
    +-------+
    |None   |
    +-------+
    	

  • 一般的に、関数の戻り値の属性を引き出すためにその関数構文を 使用しなければいけない理由は、関数の呼び出しが組合わさった時、 反映用のもう一方の(ドット)文法をパーサが理解する事ができない ためです。

    SELECT new_emp().name AS nobody;
    NOTICE:parser: syntax error at or near "."
    	

SQL問合せ言語のコマンドの集まりはすべて、 一緒にまとめて、1つの関数として定義することが可能です。 コマンドは更新系(INSERT文、UPDATE文、 DELETE文など)や、SELECT文を 使用できます。しかし、関数の最後のコマンドは関数の返り型として 指定されたものを返すSELECT文である必要があります。

CREATE FUNCTION clean_EMP () 
    RETURNS int4
    AS 'DELETE FROM EMP 
        WHERE EMP.salary <= 0;
        SELECT 1 AS ignore_this;'
    LANGUAGE 'sql';

SELECT clean_EMP();

+--+
|x |
+--+
|1 |
+--+