2012年12月20日

次期バージョンの9.3で実装された更新可能ビューを試してみる

PostgreSQL Advent Calendar 2012(全部俺)のDay 20です。

先日、ネット上でも少し話題になっていましたが、開発中のPostgreSQLの次バージョン(9.3)に「更新可能ビュー」をサポートするコードがコミットされました。

pgsql: Support automatically-updatable views.
http://archives.postgresql.org/pgsql-committers/2012-12/msg00154.php

今回はこの更新可能ビューについて、その制約なども含めてどのようなものなのかを見てみたいと思います。

なお、今回は開発中のバージョンで試しますので、試してみたい方はPostgreSQLのGitレポジトリからソースコードを取得して自身でビルドしてください。

Working with Git - PostgreSQL wiki
http://wiki.postgresql.org/wiki/Working_with_Git

■「更新可能ビュー」とは


「更新可能ビュー」とはどういった機能でしょうか。

RDBMSにおけるビューのしくみを理解している方は、それを思い浮かべていただければ分かるかと思いますが、ビューの定義が「十分に簡単」である場合、ビューに対するINSERTやUPDATE、DELETE処理は、元テーブルに対する更新処理に書き換えることが可能になるはずです。

この「ビューに対する更新処理を、元テーブルに対する更新処理へ自動的に書き換える」という機能が、「automatically updatable、自動的に更新できる」ビューと言われる機能になります。

■更新可能ビューの制約・前提条件


更新可能ビューが実装されたとは言え、「十分に簡単」という条件が付いているように、すべてのビューに対して更新が可能なわけではなく、その制約・前提条件があります。

なぜなら、「ビューへの更新処理」というのは、ビューの定義から参照元テーブルのレコード構造、つまり通常のビューとは「逆方向」に展開・変換する作業に他なりませんので、論理的に元テーブルのレコードを再構成できないと実行ができないのです。

更新可能ビューとしての前提条件は、開発版のオフィシャルマニュアルに記載がありましたので、以下に簡単に日本語に翻訳してきます。
  • 単一のテーブル、または更新可能ビューのみをFROM句に持つこと。(結合をしていないこと)
  • ビューの定義がWITH、DISTINCT、GROUP BY、HAVING、LIMIT、OFFSETを持たないこと。
  • ビューの定義がUNION、INTERSECT、EXCEPTを持たないこと。
  • ビューの定義のselectリストに出てくるカラムが、元テーブルのカラムをそのまま参照していること。expressionやリテラル、関数であってはならない。システムカラムであってもならない。
  • 元テーブルのカラムが二回以上ビューの定義に出てこないこと。
  • ビューがsecurity_barrier属性を持たないこと。
この辺りの制約・前提条件は、他のRDBMSと似ているところかもしれません。

詳細については、オフィシャルマニュアルの記載を参照してください。

PostgreSQL: Documentation: devel: CREATE VIEW
http://www.postgresql.org/docs/devel/static/sql-createview.html

■更新可能ビューを使ってみる


では最初に、もっとも基本的なビューを定義してレコードを更新できるか試してみます。
[snaga@devsv02 updatableview]$ /tmp/pgsql/bin/psql testdb
psql (9.3devel)
Type "help" for help.

testdb=# CREATE TABLE t1 ( uid INTEGER PRIMARY KEY, uname TEXT NOT NULL );
CREATE TABLE
testdb=# CREATE VIEW v1 AS SELECT * FROM t1;
CREATE VIEW
testdb=# select * FROM t1;
 uid | uname
-----+-------
(0 rows)

testdb=# 
まず、元テーブルに対して1件レコードをINSERTします。
testdb=# insert into t1 values ( 101, 'Park Gyu-Ri' );
INSERT 0 1
testdb=# select * from t1;
 uid |    uname
-----+-------------
 101 | Park Gyu-Ri
(1 row)

testdb=# select * from v1;
 uid |    uname
-----+-------------
 101 | Park Gyu-Ri
(1 row)

testdb=# 
当然、元テーブルからもビューからもレコードが見えます。

次に、今度はビューに対して2件目のレコードをINSERTしてみます。
testdb=# insert into v1 values ( 102, 'Han Seung-Yeon' );
INSERT 0 1
testdb=# select * from t1;
 uid |     uname
-----+----------------
 101 | Park Gyu-Ri
 102 | Han Seung-Yeon
(2 rows)

testdb=# select * from v1;
 uid |     uname
-----+----------------
 101 | Park Gyu-Ri
 102 | Han Seung-Yeon
(2 rows)

testdb=# 
ビューに対するレコードのINSERTも成功し、元テーブルに対してレコードがINSERTされていることが分かります。この時の実行プランを見てみると、INSERT文にビューv1を指定しているにも関わらず、内部的にはテーブルt1に更新しようとしていることが分かります。
testdb=# explain insert into v1 values ( 103, 'Nicole' );
                   QUERY PLAN
------------------------------------------------
 Insert on t1  (cost=0.00..0.01 rows=1 width=0)
   ->  Result  (cost=0.00..0.01 rows=1 width=0)
(2 rows)

testdb=# 
ビューv1に対するUPDATEについても、同様にテーブルt1への更新として内部的に書き換えられています。
testdb=# explain update v1 set uname = 'Nicole' where uid = 102;
                               QUERY PLAN
-------------------------------------------------------------------------
 Update on t1  (cost=0.00..8.27 rows=1 width=10)
   ->  Index Scan using t1_pkey on t1  (cost=0.00..8.27 rows=1 width=10)
         Index Cond: (uid = 102)
(3 rows)

testdb=#

■ビューのselectリストに含まれないカラム


次に、ビューのselectリストに含まれないカラムがあった場合に、どのように扱われるのかを見てみます。

「ビューのselectリストに含まれない」ということは、ビューへの更新時に値が存在していないため元テーブルに値を渡せない、という状況になるはずです。

以下の例では、uidとunameという元テーブルのカラムのうち、uidカラムだけを使ってビューを定義し、そのビューに対して更新(INSERT)を行おうとしています。
testdb=# create view v2 as select uid from t1;
CREATE VIEW
testdb=# insert into v2 values ( 103 );
ERROR:  null value in column "uname" violates not-null constraint
DETAIL:  Failing row contains (103, null).
STATEMENT:  insert into v2 values ( 103 );
testdb=#
このINSERT文はエラーになりましたが、エラーになっている理由を見ると、「元テーブルのunameカラムにnot-null制約が付いており、それに違反しているのでエラー」と言われていることが分かります。

ここで、元テーブルのunameカラムにデフォルト値を設定して、再度ビューv2に対して更新してみます。
testdb=# ALTER TABLE t1 ALTER COLUMN uname SET DEFAULT '(noname)';
ALTER TABLE
testdb=# insert into v2 values ( 103 );
INSERT 0 1
testdb=# select * from t1;
 uid |     uname
-----+----------------
 101 | Park Gyu-Ri
 102 | Han Seung-Yeon
 103 | (noname)
(3 rows)

testdb=#
今度はエラーにはならず、unameカラムにはデフォルト値の "(noname)" という値が設定されました。

ここまで見ると分かりますが、ビューのselectリストに含まれないカラムは、
  • 元テーブルのカラムにデフォルト値があればデフォルト値に設定。
  • デフォルト値が無ければnullに設定。
  • その上で、元テーブルの制約でチェックされる。
という動きになっていることが分かります。

■まとめ


今回は、次のリリースで提供されることになった「更新可能ビュー」の機能について、どのような機能なのか、そしてどのように動くのかを、簡単にではありますが見てきました。

前述した通り、すべてのビューが更新可能になるわけではありませんし、実際に使うビューはここで示したより複雑ですので、利用にはいろいろ制約があるかと思います。

しかし、場合によっては開発時に便利に使えることもあるかと思いますので、機会があればぜひ試してみていただければと思います。

では、また。

1 件のコメント:

  1. 他のRDBMSではビューが更新できるものもありますが、
    PostgreSQLでは(ルールを定義しなければ)ビューを更新できません、
    なんてセミナーで言っていたのを修正しないといけないな。

    返信削除