PostgreSQL の Materialized Views は直接 DELETE できない

## はじめに
エンジニアとして入社してから 1 ヶ月経った頃，プロダクトで扱っているユーザの不必要になったデータの総削除機能の開発タスクをやらせてもらいました．

このタスクで，データが入っている PostgreSQL のテーブルに対して DELETE することで順調に進んでいましたが，Materialized View では同じ操作でうまくいきませんでした．

この記事では，Materialized View にあるデータをどうすれば削除できるのかを紹介します．

## Materialized View でデータを DELETE 句で削除してみる
今回のタスクでは，ユーザに紐づく `user_id` のようなカラムを持っているテーブル・Materialized View を対象に削除機能開発を行いました．

### テーブルに対して DELETE 句で削除する
まず，`user_id` のカラムを持つテーブルを以下で探します．
```sql
SELECT table_name
FROM information_schema.columns
WHERE column_name = 'user_id';
```
```
table_name
----------
table1
table2
...
```
上記の結果を利用して，以下のようにテーブル内のデータを削除します．
```sql
DELETE FROM table1;
```
これは問題なく削除されます．

### Materialized View に対して DELETE 句で削除する
同じく，`user_id` のカラムを持つ Materialized View を以下で探します．
```sql
SELECT c.relname AS table_name
FROM pg_class c INNER JOIN pg_attribute a ON c.oid = a.attrelid
WHERE c.relkind = 'm' AND a.attnum > 0 AND NOT a.attisdropped AND a.attname = 'user_id';
```
```
table_name
----------
matview1
matview2
...
```
上記の結果を利用して，テーブルと同じようにデータを削除してみます．
```sql
DELETE FROM matview1;
```
すると，エラーが返ってきてしまいます．
```
ERROR: cannot change materialized view "matview1"
```

## Materialized View は置換する必要がある
改めて Materialized View とは，[PostgreSQL 16 のドキュメント](https://www.postgresql.org/docs/current/rules-materializedviews.html)によると，ビューと同じルールシステムを用いているが結果をテーブルの形で保持しているもの，です．
> Materialized views in PostgreSQL use the rule system like views do, but persist the results in a table-like form.

テーブルと違い，Materialized Views は作成後に**直接 update することができません**．これが理由で直接 `DELETE` ができませんでした．
> The main differences between [materialized views] and [tables] are that the **materialized view cannot subsequently be directly updated**.

このため，以下のクエリで Materialized View の内容を置換する必要があります．[^matview_refresh_command]

[^matview_refresh_command]: [日本語版 PostgreSQL 16 ドキュメント](https://www.postgresql.jp/document/16/html/sql-refreshmaterializedview.html)より， `REFRESH` コマンド操作を「置換」と表現しています．

```sql
REFRESH MATERIALIZED VIEW matview1;
```

### 補足：PostgreSQL での置換は完全リフレッシュされる
`REFRESH MATERIALIZED VIEW` コマンドの動作としては，[PostgreSQL 16 のドキュメント](https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html)から，Materialized View の内容を「完全」に置き換える，と書かれてあります．
> REFRESH MATERIALIZED VIEW completely replaces the contents of a materialized view.

完全リフレッシュの欠点としては，少数のデータを更新する際においても，全データの置換が実行されるため，処理時間が多くかかってしまいます．

この問題に対応するため，PostgreSQL 13 から拡張機能として [`pg_ivm`](https://github.com/sraoss/pg_ivm) が利用できます．IVM (Incremental View Maintenance) と呼ばれる手法で，増分変更のみを計算して置換します．
> Incremental View Maintenance (IVM) is a way to make materialized views up-to-date in which only incremental changes are computed and applied on views rather than recomputing the contents from scratch.

## おまけ： 特定のカラム名を持つテーブルを探す際に用いた情報について
指定のカラムが含まれるテーブル・Materialized View を取得する際に，`information_schema`，`pg_class`，`pg_attribute` を参照していました．これらの情報は今回の開発環境特有のものではなく，PostgreSQL が提供するものです．

### `information_schema` について
[`information_schema`](https://www.postgresql.org/docs/16/information-schema.html) は，データベースに定義されているオブジェクトに関する情報を保持している複数のビューで構成されているものです．
> The information schema consists of a set of views that contain information about the objects defined in the current database.

また，このスキーマは[標準 SQL ](https://www.postgresql.org/docs/current/features.html)に準拠しているため，PostgreSQL 固有の機能に関する情報は含まれません．

### `pg_` で始まる情報について
PostgreSQL は多くの RDMS のように，スキーマメタデータや内部的な情報を格納している [`system catalogs`](https://www.postgresql.org/docs/16/catalogs.html)，[`system views`](https://www.postgresql.org/docs/16/views.html) を持っています．どちらも `pg_` で始まる名前のテーブル・ビューになっています．
> The system catalogs are the place where a relational database management system stores schema metadata, such as information about tables and columns, and internal bookkeeping information.

今回利用した，[`pg_class`](https://www.postgresql.org/docs/16/catalog-pg-class.html) はテーブルを含むカラムを持っているオブジェクトについての情報を，[`pg_attribute`](https://www.postgresql.org/docs/16/catalog-pg-attribute.html) はテーブルカラムについての情報を格納しているものです．指定カラムが含まれる Materialized View はこれらの情報を利用して取得していました．

## まとめ
Materialized View のデータを削除（置換）する方法を紹介しました．`REFRESH MATERIALIZED VIEW` で更新できるのを忘れずに！