【PostgreSQL】テーブル継承は、使用上の注意をよく読んで正しく使いましょう（自戒）

<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521914/952ec450-309b-2e8e-6a99-b144adcdc8f8.png">[^1]

## はじめに
本記事は[PostgreSQL Advent Calendar 2020](https://qiita.com/advent-calendar/2020/postgresql)の13日目です。
ここではINHERITS句を用いたテーブル継承について書きます。
ドキュメントをきちんと読まずに軽い気持ちでINHERITS句を指定したところ、後に自分が盛大な勘違いをしていたことが発覚し大やけどした経験を共有したいと思います。今後同じ思いをされる方が一人でも減りますように。。

## 結論
INHERITS句を使う前に確認しておきたいことは、２点に尽きます。

1. 子テーブルへ挿入されたデータは自動的に親テーブルにも挿入されるわけではない（逆もしかり）
2. 親テーブルの主キー制約（や一意性制約）は子テーブルに継承されない

いずれも[参考](#参考)にあるようなドキュメントに５分も目を通せば明らかなことなのですが、一度間違った解釈をしてしまうと正しい理解に戻ってくるまでに時間を要しました。

実行環境はDocker公式イメージ`postgres:13.1`を使用しています。

## 子テーブルへ挿入されたデータは親テーブルにも挿入されるわけではない
オブジェクト指向のクラス継承のように、INHERITS句を使うと親テーブルが持つカラムを子テーブルに継承することができます。

```sql

CREATE TABLE company (
  security_code char(4) PRIMARY KEY,
  name varchar(256)
);

CREATE TABLE axx_company (
  name_ja varchar(256),
  address varchar(256)
) INHERITS (company);

CREATE TABLE bxx_company (
  isin_code char(12)
) INHERITS (company);
```

```
postgres=# \d axx_company
                       Table "public.axx_company"
    Column     |          Type          | Collation | Nullable | Default
---------------+------------------------+-----------+----------+---------
 security_code | character(4)           |           | not null |
 name          | character varying(256) |           |          |
 name_ja       | character varying(256) |           |          |
 address       | character varying(256) |           |          |
Inherits: company
```

やりたいこととしては、データセットAxxとデータセットBxxからとってきた会社のデータをそれぞれ`axx_company`テーブルと`bxx_company`テーブルへ格納し、親の`company`テーブルで全会社を登録されている状態を作るというものでした。実際にデータを挿入してみるとそのような見え方をします。

```sql

INSERT INTO axx_company VALUES ('1111', '1 Corp.', '株式会社１', '東京都新宿区１丁目');
INSERT INTO axx_company VALUES ('2222', '2 Corp.', '株式会社２', '東京都新宿区２丁目');
INSERT INTO bxx_company VALUES ('3333', '3 Inc.', 'JP0000033333');
```

```
postgres=# SELECT * FROM company;
 security_code |  name
---------------+---------
 1111          | 1 Corp.
 2222          | 2 Corp.
 3333          | 3 Inc.
(3 rows)
```

ここで当時の私はとんだ勘違いをしました。「なるほど、子テーブルに挿入されたデータが射影されて親テーブルにも入るのか」と。。

・・なわけではありません。実際には、親テーブルの中は空っぽで、`SELECT * FROM company`が下のクエリの要領で継承したテーブルたちのレコードを取得してきているということでした。

```sql

SELECT security_code, name FROM company
  UNION
SELECT security_code, name FROM axx_company
  UNION
SELECT security_code, name FROM bxx_company;
```

親テーブルへのSELECTは、透過的に子（孫）テーブルのレコードまで参照することができます。UPDATEやDELETEも、同様に親テーブルにかけた更新が子テーブルにまで及びます。しかしながらINSERTは、あくまでも指定したテーブルのみにレコードが挿入されます。子テーブルに挿入すれば自動的に親テーブルにも挿入される！というわけではありません。同様に親テーブルに挿入しても子テーブルには挿入されません。

また、下のように`users`テーブルが`company`テーブルを外部キー参照しているとき、子の`axx_company`テーブルに存在する値でINSERTを試みても親の`company`テーブルに存在しなければ参照制約エラーが返ってきます。
<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521914/d2549fca-90f3-206c-a887-d32e990e6019.png">

```
postgres=# INSERT INTO users VALUES (1, '名無し', '1111');
ERROR:  insert or update on table "users" violates foreign key constraint "users_security_code_fkey"
DETAIL:  Key (security_code)=(1111) is not present in table "company".
```

## 親テーブルの主キー制約は子テーブルに継承されない
親テーブルで宣言した主キー制約は子テーブルに継承されません。同様に一意性制約や外部キー制約も継承されません。親テーブルの主キー制約は子テーブルには非NULL制約のみが引き継がれます。なので`ALTER TABLE bxx_company`して主キー制約を別途つけてあげなければ、下のような現象も容易に起こりえます。

```
postgres=# SELECT * FROM bxx_company;
 security_code |  name  |  isin_code
---------------+--------+--------------
 3333          | 3 Inc. | JP0000033333
 3333          | 3 Inc. | JP0000033333
```

さらに注意すべきは親の`company`テーブルの主キー制約です。

```
postgres=# SELECT * FROM company;
 security_code |  name
---------------+---------
 1111          | 1 Corp.
 2222          | 2 Corp.
 3333          | 3 Inc.
 3333          | 3 Inc.
(4 rows)
```

`company.security_code`には一意性制約があるので一見これはあり得ないように見えますが、あくまでも`company`テーブルの主キー制約は`company`テーブル自身のみにかかるものなので、子テーブル内（もしくは子テーブル間）で重複しようとも親テーブルはお構いなしです。


## 伝統的なテーブル継承
オブジェクト指向の継承関係をINHERITS句を用いたテーブル継承で実装してみようとすると、専らサブタイプへデータを挿入したい場合には使い勝手があまり良くありません。
リレーショナルデータベースでテーブル継承を表現する伝統的な方法として、PofEAAや『SQLアンチパターン』で紹介されているものは、シングルテーブル継承・具象テーブル継承・クラステーブル継承と呼ばれるものです[^2]。
使用するフレームワークやO/Rマッパーのライブラリによってどの方式をとるかは変わってくると思いますが、一例としてクラステーブル継承のかたちを取ると今回の例では下のようなER図になります。
サブタイプごとに１つずつテーブルを持ち、スーパータイプのテーブルに対する外部キーの役割を果たす主キーを設定します。利点としては`company`テーブルに全会社のデータが入ることが保証されるので、`company`テーブルと他のテーブルを関連させたいというときにも効率よく実現することができます。

<img width=500 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521914/a611edea-bf91-6563-5435-2054d263abfe.png">


## まとめ
PostgreSQLのINHERITS句に関して犯した勘違いについて書いてみました。
この記事を書きながら、冷静に考えてみたら色々つじつまが合わないのに当時の自分はなんでそんな不思議な解釈をしてしまったのかと情けなく思いました。
飛びつく前にきちんと調べないと・・
ただ一通り調べてみると、結局このINHERITS句を使うテーブル継承のありがたみがいまひとつ実感できませんでした。どういった場合に真価を発揮してくれるのでしょうか？


## 参考
本記事はINHERITS句の機能の一部にしか焦点を当てていません。実際の仕様の全貌については、以下を参考にしてください。

* [[公式] PostgreSQL: Documentation: 13: 5.10. Inheritance](https://www.postgresql.org/docs/current/ddl-inherit.html)
* [[DBOnline] テーブルを継承して新しいテーブルを作成する(CREATE TABLE ... INHERITS)](https://www.dbonline.jp/postgresql/table/index13.html)

明日は@yugo-nさんです！

[^1]: `security_code`は証券（銘柄）コード、`isin_code`は国際証券識別番号を表しています。
[^2]: 詳しくは[単一テーブル継承・クラステーブル継承・具象クラス継承について](https://qiita.com/bmf_san/items/a03820b14a72db618d15)