ビューとはSQLの観点から見るとテーブルと同じもの。テーブルなのかビューなのかを意識する必要はない。ビューとテーブルの違いは**「実際のデータを保存しているか否か」**だけである。 テーブルを作ってINSERT文でデータを格納するとデータベースにデータが保存される。(記憶装置、ハードディスク) ビューではデータを記憶装置に保存しない。どこにも保存せずにSELECT文そのものを実行して一時的に仮想テーブルを作っている。
① データを保存しないので記憶装置の容量を節約できる。
② 頻繁に使うSELECT文を毎回書かずに、ビューとして保存して置くことで使い回しができる。ビューが含むデータは本のテーブルと連動しているので自動で最新状態に更新されている。
CREATE VIEW文を使ってビューを作成する。CREATE VIEWの構文は下記のようになる。ASキーワードの後ろにはSELECT文を記述する。ビュー名の後ろにビューの列名をリストで定義する。SELECT文の最初の列はビューの1番目の列、となる。 ビューの定義にはどんなSELECT文も書くことができる。(WHERE、GROUP BY、HAVINGも使える)
CREATE VIEW ビュー名 (<ビューの列名1>, <ビューの列名2>, .....)
AS
<SELECT文>
実際にShohinテーブルのデータを用いてShohinSumという名前のビューを作ってみる。
postgres=# CREATE VIEW ShohinSum (shohin_bunrui, cnt_shohin)
postgres-# AS
postgres-# SELECT shohin_bunrui, COUNT(*)
postgres-# FROM Shohin
postgres-# GROUP BY shohin_bunrui;
CREATE VIEW
postgres=# SELECT shohin_bunrui, cnt_shohin
postgres-# FROM ShohinSum;
 shohin_bunrui | cnt_shohin 
---------------+------------
 キッチン用品  |          4
 衣服          |          2
 事務用品      |          2
(3 rows)
ビューをFROM句に指定したときの検索は、まず最初にビューに定義されたSELECT文が実行される。そしてその結果に対して、ビューをFROM句に指定したSELECT文が実行される。このように2段階を踏む。ビューをもとにビューを作るということもできるけど推奨されていない。(パフォーマンス低下を招くから)
ビューの定義でORDER BYは使えない
ビュー定義のSELECT文でいくつかの条件を満たしているとビューに対する更新可能
→ 集約されたビューは更新不可能!!
ビューを更新できるケースは集約なしのビュー。下記のSELECT文には集約も結合もない文。
postgres=# CREATE VIEW ShohinJim (shohin_id, shohin_mei, shohin_bunrui, hanbai_tanka, shiire_tanka, torokubi)
postgres-# AS
postgres-# SELECT * FROM Shohin WHERE shohin_bunrui = '事務用品';
CREATE VIEW
postgres=# SELECT * FROM ShohinJim;
 shohin_id |  shohin_mei  | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+--------------+---------------+--------------+--------------+------------
 0002      | 穴あけパンチ | 事務用品      |          500 |          320 | 2009-09-11
 0008      | ボールペン   | 事務用品      |          100 |              | 2009-11-11
(2 rows)
postgres=# INSERT INTO ShohinJim VALUES ('0009', '印鑑', '事務用品', 95, 10, '2009-11-30');
INSERT 0 1
postgres=# SELECT * FROM ShohinJim;
 shohin_id |  shohin_mei  | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+--------------+---------------+--------------+--------------+------------
 0002      | 穴あけパンチ | 事務用品      |          500 |          320 | 2009-09-11
 0008      | ボールペン   | 事務用品      |          100 |              | 2009-11-11
 0009      | 印鑑         | 事務用品      |           95 |           10 | 2009-11-30
(3 rows)
// 元のテーブルにも追加されている
postgres=# SELECT * FROM Shohin;
 shohin_id |   shohin_mei   | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+----------------+---------------+--------------+--------------+------------
 0001      | Tシャツ        | 衣服          |         1000 |          500 | 2009-09-20
 0002      | 穴あけパンチ   | 事務用品      |          500 |          320 | 2009-09-11
 0003      | カッターシャツ | 衣服          |         4000 |         2800 | 
 0004      | 包丁           | キッチン用品  |         3000 |         2800 | 2009-09-20
 0005      | 圧力鍋         | キッチン用品  |         6800 |         5000 | 2009-01-15
 0006      | フォーク       | キッチン用品  |          500 |              | 2009-09-20
 0007      | おろしがね     | キッチン用品  |          880 |          790 | 2008-04-28
 0008      | ボールペン     | 事務用品      |          100 |              | 2009-11-11
 0009      | 印鑑           | 事務用品      |           95 |           10 | 2009-11-30
(9 rows)
shohin_bunruiが事務用品でないものをShohinJimのビューに入れてみた。エラーは出ず、元のテーブルにshohin_idが0009のデータが追加された。もちろんShohinJimのビューには表示されていない。
postgres=# INSERT INTO ShohinJim VALUES ('0009', '印鑑', 'キッチン用品', 95, 10, '2009-11-30');
INSERT 0 1
postgres=# SELECT * FROM ShohinJim;
 shohin_id |  shohin_mei  | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+--------------+---------------+--------------+--------------+------------
 0002      | 穴あけパンチ | 事務用品      |          500 |          320 | 2009-09-11
 0008      | ボールペン   | 事務用品      |          100 |              | 2009-11-11
(2 rows)
postgres=# SELECT * FROM Shohin;
 shohin_id |   shohin_mei   | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+----------------+---------------+--------------+--------------+------------
 0001      | Tシャツ        | 衣服          |         1000 |          500 | 2009-09-20
 0002      | 穴あけパンチ   | 事務用品      |          500 |          320 | 2009-09-11
 0003      | カッターシャツ | 衣服          |         4000 |         2800 | 
 0004      | 包丁           | キッチン用品  |         3000 |         2800 | 2009-09-20
 0005      | 圧力鍋         | キッチン用品  |         6800 |         5000 | 2009-01-15
 0006      | フォーク       | キッチン用品  |          500 |              | 2009-09-20
 0007      | おろしがね     | キッチン用品  |          880 |          790 | 2008-04-28
 0008      | ボールペン     | 事務用品      |          100 |              | 2009-11-11
 0009      | 印鑑           | キッチン用品  |           95 |           10 | 2009-11-30
(9 rows)
ビューの削除にはDROP VIEW文を使う。
postgres=# DROP VIEW ShohinSum;
DROP VIEW
postgres=# SELECT * FROM ShohinSum;
ERROR:  relation "shohinsum" does not exist
LINE 1: SELECT * FROM ShohinSum;
                      ^
先ほど追加したデータをShohinテーブルから削除しておいた。ShohinJimビューからもなくなっているのがわかる。
postgres=# DELETE FROM Shohin WHERE shohin_id = '0009';
DELETE 1
postgres=# SELECT * FROM ShohinJim;
 shohin_id |  shohin_mei  | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+--------------+---------------+--------------+--------------+------------
 0002      | 穴あけパンチ | 事務用品      |          500 |          320 | 2009-09-11
 0008      | ボールペン   | 事務用品      |          100 |              | 2009-11-11
(2 rows)
postgres=# SELECT * FROM Shohin;
 shohin_id |   shohin_mei   | shohin_bunrui | hanbai_tanka | shiire_tanka |  torokubi  
-----------+----------------+---------------+--------------+--------------+------------
 0001      | Tシャツ        | 衣服          |         1000 |          500 | 2009-09-20
 0002      | 穴あけパンチ   | 事務用品      |          500 |          320 | 2009-09-11
 0003      | カッターシャツ | 衣服          |         4000 |         2800 | 
 0004      | 包丁           | キッチン用品  |         3000 |         2800 | 2009-09-20
 0005      | 圧力鍋         | キッチン用品  |         6800 |         5000 | 2009-01-15
 0006      | フォーク       | キッチン用品  |          500 |              | 2009-09-20
 0007      | おろしがね     | キッチン用品  |          880 |          790 | 2008-04-28
 0008      | ボールペン     | 事務用品      |          100 |              | 2009-11-11
(8 rows)
サブクエリとは使い捨てのビュー。ビュー定義のSELECT文をそのままFROM句に持ち込んだもの。
実際にサブクエリで表現してみる。
postgres=# SELECT shohin_bunrui, cnt_shohin
postgres-# FROM (SELECT shohin_bunrui, COUNT(*) AS cnt_shohin
postgres(# FROM Shohin
postgres(# GROUP BY shohin_bunrui) AS ShohinSum;
 shohin_bunrui | cnt_shohin 
---------------+------------
 キッチン用品  |          4
 衣服          |          2
 事務用品      |          2
(3 rows)
上記の処理をビュー定義で行ったとき。得られる結果は同じ。
postgres=# CREATE VIEW ShohinSum (shohin_bunrui, cnt_shohin)
postgres-# AS
postgres-# SELECT shohin_bunrui, COUNT(*)
postgres-# FROM Shohin
postgres-# GROUP BY shohin_bunrui;
CREATE VIEW
postgres=# SELECT shohin_bunrui, cnt_shohin
postgres-# FROM ShohinSum;
 shohin_bunrui | cnt_shohin 
---------------+------------
 キッチン用品  |          4
 衣服          |          2
 事務用品      |          2
(3 rows)
ビュー定義のSELECT文をそのままFROM句に入れてしまったのがサブクエリ。サブクエリにもASキーワードを使って名前をつけるが、これは使い捨ての名前(ASは省略可)。実行終了後には消えて無くなる。**内側のSELECT文から実行されていく。**サブクエリの階層数には制限がなくどんどん入れ子にしていくことはできるが、パフォーマンスが悪いので避けた方がいい。
**スカラ・サブクエリとは必ず1行1列だけの戻り値を返すという制限をつけたサブクエリのこと。**戻り値が一個だけなので比較演算子の入力として使われる。
実際にWHERE句で使ってみる。 (SELECT AVG(hanbai_tanka) FROM Shohin)の部分が平均のhanbai_tankaを求めるスカラ・サブクエリ。最初にサブクエリから実行、その後に外側が実行される。
postgres=# SELECT shohin_id, shohin_mei, hanbai_tanka
FROM Shohin
WHERE hanbai_tanka > (SELECT AVG(hanbai_tanka) FROM Shohin);
 shohin_id |   shohin_mei   | hanbai_tanka 
-----------+----------------+--------------
 0003      | カッターシャツ |         4000
 0004      | 包丁           |         3000
 0005      | 圧力鍋         |         6800
(3 rows)
集約関数をWHERE句では使えないので下記のようにスカラ・サブクエリを使わないで書くとエラーになる。
postgres=# SELECT shohin_id, shohin_mei, hanbai_tanka
postgres-# FROM Shohin
postgres-# WHERE hanbai_tanka > AVG(hanbai_tanka);
ERROR:  aggregate functions are not allowed in WHERE
LINE 3: WHERE hanbai_tanka > AVG(hanbai_tanka);
   
スカラサブクエリは定数や列名をかけるところならどこにでも書くことができる。下記はSELECT句で使った例。
postgres=# SELECT shohin_id, shohin_mei, hanbai_tanka, (SELECT AVG(hanbai_tanka) FROM Shohin) AS avg_tanka FROM Shohin;
 shohin_id |   shohin_mei   | hanbai_tanka |       avg_tanka       
-----------+----------------+--------------+-----------------------
 0001      | Tシャツ        |         1000 | 2097.5000000000000000
 0002      | 穴あけパンチ   |          500 | 2097.5000000000000000
 0003      | カッターシャツ |         4000 | 2097.5000000000000000
 0004      | 包丁           |         3000 | 2097.5000000000000000
 0005      | 圧力鍋         |         6800 | 2097.5000000000000000
 0006      | フォーク       |          500 | 2097.5000000000000000
 0007      | おろしがね     |          880 | 2097.5000000000000000
 0008      | ボールペン     |          100 | 2097.5000000000000000
(8 rows)
次はHAVING句で使用した例。shohin_bunruiごとに計算した平均値が全体のhanbai_tankaよりも高かったら出力される。(SELECT AVG(hanbai_tanka) FROM Shohin)がスカラ・サブクエリで、全体の平均hanbai_tankaを表している。
postgres=# SELECT shohin_bunrui, AVG(hanbai_tanka)
postgres-# FROM Shohin
postgres-# GROUP BY shohin_bunrui
postgres-# HAVING AVG(hanbai_tanka) > (SELECT AVG(hanbai_tanka) FROM Shohin);
 shohin_bunrui |          avg          
---------------+-----------------------
 キッチン用品  | 2795.0000000000000000
 衣服          | 2500.0000000000000000
(2 rows)
相関サブクエリはテーブル全体ではなく、テーブルの一部のレコード集合に限定した比較をしたい場合に使う。ここでS1やS2は何を意味しているのかというと、今回比較対象となるテーブルが同じShohinテーブルなので区別するために別名をつけた。この別名(相関名)はサブクエリ内部でしか使うことができない。
postgres=# SELECT shohin_bunrui, shohin_mei, hanbai_tanka
postgres-# FROM Shohin AS S1
postgres-# WHERE hanbai_tanka > (SELECT AVG(hanbai_tanka) FROM Shohin AS S2
postgres(# WHERE S1.shohin_bunrui = S2.shohin_bunrui
postgres(# GROUP BY shohin_bunrui);
 shohin_bunrui |   shohin_mei   | hanbai_tanka 
---------------+----------------+--------------
 事務用品      | 穴あけパンチ   |          500
 衣服          | カッターシャツ |         4000
 キッチン用品  | 包丁           |         3000
 キッチン用品  | 圧力鍋         |         6800
(4 rows)
5.1
CREATE VIEW ViewRenshu5_1(shohin_mei, hanbai_tanka, torokubi)
AS 
SELECT shohin_mei, hanbai_tanka, torokubi
FROM Shohin
WHERE torokubi = '2009-09-20' AND hanbai_tanka >= 1000;
CREATE VIEW
SELECT * FROM ViewRenshu5_1;
 shohin_mei | hanbai_tanka |  torokubi  
------------+--------------+------------
 Tシャツ    |         1000 | 2009-09-20
 包丁       |         3000 | 2009-09-20
(2 rows)
5.2 ShohinテーブルのNOT NULL制約がshohin_idにかかっているので、実行するとエラーが出る。
postgres=# INSERT INTO ViewRenshu5_1 VALUES ('ナイフ', 300, '2009-11-02');
ERROR:  null value in column "shohin_id" of relation "shohin" violates not-null constraint
DETAIL:  Failing row contains (null, ナイフ, null, 300, null, 2009-11-02).
5.3
SELECT shohin_id, shohin_mei, shohin_bunrui, hanbai_tanka,
(SELECT AVG(hanbai_tanka) FROM Shohin) AS hanbai_tanka_all FROM Shohin;
 shohin_id |   shohin_mei   | shohin_bunrui | hanbai_tanka |   hanbai_tanka_all    
-----------+----------------+---------------+--------------+-----------------------
 0001      | Tシャツ        | 衣服          |         1000 | 2097.5000000000000000
 0002      | 穴あけパンチ   | 事務用品      |          500 | 2097.5000000000000000
 0003      | カッターシャツ | 衣服          |         4000 | 2097.5000000000000000
 0004      | 包丁           | キッチン用品  |         3000 | 2097.5000000000000000
 0005      | 圧力鍋         | キッチン用品  |         6800 | 2097.5000000000000000
 0006      | フォーク       | キッチン用品  |          500 | 2097.5000000000000000
 0007      | おろしがね     | キッチン用品  |          880 | 2097.5000000000000000
 0008      | ボールペン     | 事務用品      |          100 | 2097.5000000000000000
(8 rows)
5.4
SELECT shohin_id, shohin_mei, shohin_bunrui, hanbai_tanka,
(SELECT AVG(hanbai_tanka) FROM Shohin AS S2
WHERE S1.shohin_bunrui = S2.shohin_bunrui)
FROM Shohin AS S1;
 shohin_id |   shohin_mei   | shohin_bunrui | hanbai_tanka |          avg          
-----------+----------------+---------------+--------------+-----------------------
 0001      | Tシャツ        | 衣服          |         1000 | 2500.0000000000000000
 0002      | 穴あけパンチ   | 事務用品      |          500 |  300.0000000000000000
 0003      | カッターシャツ | 衣服          |         4000 | 2500.0000000000000000
 0004      | 包丁           | キッチン用品  |         3000 | 2795.0000000000000000
 0005      | 圧力鍋         | キッチン用品  |         6800 | 2795.0000000000000000
 0006      | フォーク       | キッチン用品  |          500 | 2795.0000000000000000
 0007      | おろしがね     | キッチン用品  |          880 | 2795.0000000000000000
 0008      | ボールペン     | 事務用品      |          100 |  300.0000000000000000
(8 rows)
上記はビューを作ったのではなく、サブクエリを作っただけ。問題はビューを作るだったので変更。
CREATE VIEW AvgTankaByBunrui AS
SELECT shohin_id, shohin_mei, shohin_bunrui, hanbai_tanka,
(SELECT AVG(hanbai_tanka) FROM Shohin AS S2
WHERE S1.shohin_bunrui = S2.shohin_bunrui) AS avg_hanbai_tanka
FROM Shohin AS S1;
CREATE VIEW
SELECT * FROM AvgTankaByBunrui;
 shohin_id |   shohin_mei   | shohin_bunrui | hanbai_tanka |   avg_hanbai_tanka    
-----------+----------------+---------------+--------------+-----------------------
 0001      | Tシャツ        | 衣服          |         1000 | 2500.0000000000000000
 0002      | 穴あけパンチ   | 事務用品      |          500 |  300.0000000000000000
 0003      | カッターシャツ | 衣服          |         4000 | 2500.0000000000000000
 0004      | 包丁           | キッチン用品  |         3000 | 2795.0000000000000000
 0005      | 圧力鍋         | キッチン用品  |         6800 | 2795.0000000000000000
 0006      | フォーク       | キッチン用品  |          500 | 2795.0000000000000000
 0007      | おろしがね     | キッチン用品  |          880 | 2795.0000000000000000
 0008      | ボールペン     | 事務用品      |          100 |  300.0000000000000000
(8 rows)