@@ -83,19 +83,20 @@ postgres=# SELECT * FROM people;
8383| 検証項目 | 結果 |
8484| :--- | :--- |
8585| 1. 生成列で自分自身の列を参照できるか | 不可(生成列は生成列を参照できないた) |
86- | 2. 他のテーブルの列を参照できるか | 不可(サブクエリの利用は不可) |
87- | 3. ネストした生成列定義は可能か | 不可(生成列は生成列を参照できないため) |
88- | 4. 計算途中でnull値が混入したらどうなるか | 普通のクエリと同じように ` null ` になる |
89- | 5. NOT NULL制約を付けることができるか | 可能。仮想生成列も登録時チェックになる |
90- | 6. 生成列は一意制約を付けることができるか | 格納生成列は可能。仮想生成列は不可。式インデックスで代用 |
91- | 7. 生成列はインデックスに使えるか | 格納生成列は可能。仮想生成列は式インデックスで代用 |
92- | 8. 生成列はPKにできるか | 格納生成列は可能。仮想生成列はインデックスを持てないので不可 |
93- | 9. 生成列はパーティションキーに使えるか | 不可(` STORED ` / ` VIRTUAL ` 共に不可) |
94- | 10. テーブル作成後に後から生成列を追加できるか | 可能。AccessExclusiveLock を取る |
95- | 11. NOT NULL制約を付けた生成列を後から追加すると処理時間はどうなるか | ` NOT NULL ` 検証のためのテーブルフルスキャンが発生し、変更時間が長くなる |
96- | 12. 生成列の定義変更はできるか | 不可(` DROP COLUMN ` & ` ADD COLUMN ` で対応する必要がある) |
97- | 13. 利用しているカラムをRENAMEしたら? | PostgreSQLが自動で定義式を追随・更新してくれる |
98- | 14. 利用しているカラムをDROPしたら? | エラーになる。 ` CASCADE ` を付けると依存した列ごと削除可能) |
86+ | 2. IDENTITY列を参照できるか | 可能(格納・仮想の両方で可能) |
87+ | 3. 他のテーブルの列を参照できるか | 不可(サブクエリの利用は不可) |
88+ | 4. ネストした生成列定義は可能か | 不可(生成列は生成列を参照できないため) |
89+ | 5. 計算途中でnull値が混入したらどうなるか | 普通のクエリと同じように ` null ` になる |
90+ | 6. NOT NULL制約を付けることができるか | 可能。仮想生成列も登録時チェックになる |
91+ | 7. 生成列は一意制約を付けることができるか | 格納生成列は可能。仮想生成列は不可。式インデックスで代用 |
92+ | 8. 生成列はインデックスに使えるか | 格納生成列は可能。仮想生成列は式インデックスで代用 |
93+ | 9. 生成列はPKにできるか | 格納生成列は可能。仮想生成列はインデックスを持てないので不可 |
94+ | 10. 生成列はパーティションキーに使えるか | 不可(` STORED ` / ` VIRTUAL ` 共に不可) |
95+ | 11. テーブル作成後に後から生成列を追加できるか | 可能。AccessExclusiveLock を取る |
96+ | 12. NOT NULL制約を付けた生成列を後から追加すると処理時間はどうなるか | ` NOT NULL ` 検証のためのテーブルフルスキャンが発生し、変更時間が長くなる |
97+ | 13. 生成列の定義変更はできるか | 不可(` DROP COLUMN ` & ` ADD COLUMN ` で対応する必要がある) |
98+ | 14. 利用しているカラムをRENAMEしたら? | PostgreSQLが自動で定義式を追随・更新してくれる |
99+ | 15. 利用しているカラムをDROPしたら? | エラーになる。 ` CASCADE ` を付けると依存した列ごと削除可能) |
99100
100101## 1. 生成列で自分自身の列を参照できるか
101102
@@ -116,7 +117,36 @@ DETAIL: A generated column cannot reference another generated column.
116117
117118結果はNGです。「生成列は他の生成列を参照できない」とありますね。実現したいことも意味不明なので、失敗して当然なので想定通りかなと。この結果は、` VIRTUAL ` を ` STORED ` に変えても同じです。
118119
119- ## 2. 他のテーブルの列を参照できるか
120+ ## 2. IDENTITY列を参照できるか
121+
122+ IDENTITY列(シリアル)を参照できるか確認します。
123+
124+ ``` sql
125+ postgres= # CREATE TABLE m_product (
126+ item_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,
127+ product_name TEXT ,
128+
129+ -- 格納生成列
130+ item_code_stored TEXT GENERATED ALWAYS AS (item_id* 10 ) STORED,
131+
132+ -- 仮想生成列
133+ item_code_virtual TEXT GENERATED ALWAYS AS (item_id* 100 ) VIRTUAL
134+ );
135+ CREATE TABLE
136+
137+ postgres= # INSERT INTO m_product (product_name) VALUES ('Apple');
138+ INSERT 0 1
139+
140+ postgres= # SELECT * FROM m_product;
141+ item_id | product_name | item_code_stored | item_code_virtual
142+ -- -------+--------------+------------------+-------------------
143+ 1 | Apple | 10 | 100
144+ (1 row)
145+ ```
146+
147+ 格納生成列・仮想生成列のどちらも問題なく、IDENTITY列を参照することができました。
148+
149+ ## 3. 他のテーブルの列を参照できるか
120150
121151まず、税率を保持するテーブルを作成します。
122152
@@ -148,7 +178,7 @@ LINE 8: base_price * (1.0 + (SELECT tax_rate FROM m_tax WHER...
148178
149179[ ドキュメント] ( https://www.postgresql.org/docs/18/sql-createtable.html#SQL-CREATETABLE-PARMS-GENERATED-STORED ) にも ` References to other tables are not allowed. ` (他のテーブルは参照できない)と書いていますので、その通りの結果です。格納生成列、仮想生成列ともに結果は変わりません。
150180
151- ## 3 . ネストした生成列定義は可能か
181+ ## 4 . ネストした生成列定義は可能か
152182
153183「割引額」という生成列を参照する、「料金」という生成列の定義を試みます。
154184
@@ -174,7 +204,7 @@ DETAIL: A generated column cannot reference another generated column.
174204
175205ドキュメントにも、 ` The generation expression can refer to other columns in the table, but not other generated columns. ` (生成式はテーブル内の他の列を参照できますが、他の生成列を参照することはできません。)とあるので、記載通りの挙動です。
176206
177- ## 4 . 計算途中でnull値が混入したらどうなるか
207+ ## 5 . 計算途中でnull値が混入したらどうなるか
178208
179209例えば、総額=単価x数量 という生成列を定義します。この時、単価がNULLの場合にはどのように挙動するか確かめます。
180210
@@ -201,7 +231,7 @@ SELECT * FROM t_order;
201231
202232unit_priceがNULLの場合は、total_priceもNULLという結果です。SQL的に自然な挙動ですね。回避するには、unit_priceやquantityにNOT NULL制約を付けたり、COALESCEでNULLを実値に置き換える必要があります。
203233
204- ## 5 . NOT NULL制約を付けることができるか
234+ ## 6 . NOT NULL制約を付けることができるか
205235
206236ちょっとテクニカルなテーブル定義に書き換えます。生成列のtotal_priceのみNOT NULL制約を付けて、ソースのunit_price, quantity はNULL許容にします。
207237
@@ -233,7 +263,7 @@ DETAIL: Failing row contains (1, null, 5, virtual).
233263
234264この検証は「11」節ではさらに詳しく調べています。
235265
236- ## 6 . 生成列は一意制約を付けることができるか
266+ ## 7 . 生成列は一意制約を付けることができるか
237267
238268格納生成列の場合は成功します。
239269
@@ -262,7 +292,7 @@ ERROR: unique constraints on virtual generated columns are not supported
262292これは後述するインデックスのサポート有無の挙動の差でしょう。なお、これまた後述する式インデックスに一意制約をつけることで、実質的に、仮想生成列に一意制約をつけることはできます。
263293
264294
265- ## 7 . 生成列はインデックスに使えるか
295+ ## 8 . 生成列はインデックスに使えるか
266296
267297格納生成列、仮想生成列それぞれにインデックスを追加してみます。
268298
@@ -318,7 +348,7 @@ WHERE full_name = 'Yamada Taro';
318348実行計画レベルで、式インデックスが使われていることを確認できました。多少の回避方法が必要ですが、仮想列も事実上、インデックスを貼れると思ってよいでしょう。
319349
320350
321- ## 8 . 生成列はPKにできるか
351+ ## 9 . 生成列はPKにできるか
322352
323353例えば、受注明細トランで、受注番号+商品IDを組み合わせてPKにするケースを考えます(普通は、サロゲートにして欲しい案件ですが、あくまで動作確認上の"例"です)。
324354
@@ -391,7 +421,7 @@ DETAIL: Key (((order_id::text || '-'::text) || item_id::text))=(1003-202) alrea
391421
392422無事動作しました。ただし、あくまで仮想列自体に一意制約+NOT NULL制約をつけたわけではなく、仮想列と同等の定義を持った式インデックスに、一意制約+NOT NULL制約をつけたことになります。そのため、外部キー制約の参照の対象にはできないでしょう。
393423
394- ## 9 . 生成列はパーティションキーに使えるか
424+ ## 10 . 生成列はパーティションキーに使えるか
395425
396426受注日時から受注日付(yyyy-MM-dd)を生成列で作成し、それをパーティションキーとするようなケースで試します。
397427
@@ -413,7 +443,7 @@ DETAIL: Column "order_date" is a generated column.
413443
414444もちろん[ ドキュメント] ( https://www.postgresql.org/docs/18/ddl-generated-columns.html#:~:text=A%20generated%20column%20cannot%20be%20part%20of%20a%20partition%20key. ) にも、` A generated column cannot be part of a partition key. ` (生成列はパーティションキーには利用できません。)と書かれています。
415445
416- ## 10 . テーブル作成後に後から生成列を追加できるか
446+ ## 11 . テーブル作成後に後から生成列を追加できるか
417447
418448` m_user ` に格納生成列、仮想生成列の順番で足してみます。
419449
@@ -439,7 +469,7 @@ ALTER TABLE
439469
440470結果は成功でした。ちなみに、ALTER文実行前にはBEGINEを実行し、別プロセスで` pg_locks ` を確認したところ、どちらも ` AccessExclusiveLock ` を取っていました。格納生成列は既存行が多ければ長時間、参照もできないので注意が必要です。仮想生成列はメタデータの書き換えのみで済むため、` AccessExclusiveLock ` を取りますが一瞬で終わります。
441471
442- ## 11 . NOT NULL制約を付けた生成列を後から追加すると処理時間はどうなるか
472+ ## 12 . NOT NULL制約を付けた生成列を後から追加すると処理時間はどうなるか
443473
444474以下の ` t_order ` に1万件のダミーデータを登録し、[ 格納|生成] x[ NOT NULL有無] の4パターンで処理時間を計測しました。
445475
@@ -502,13 +532,37 @@ ALTER TABLE t_order DROP COLUMN total_price;
502532
503533格納生成列もNOT NULL化すると少し処理時間が増します。理由を深く調査はしていませんが、NOT NULL計算分が上乗せになるからでしょう。そして、仮想生成列ですが、NOT NULL制約を追加すると大幅に時間がかかります。これはおそらくテーブルフルスキャンでNOT NULLにならないかチェックするからでしょう。
504534
505- ## 12. 生成列の定義変更はできるか
535+ ** (2025.11.7追記)**
536+
537+ ちなみに、元テーブルに生成列の計算元列にNOT NULL制約を付けると、フルスキャンが論理的にはスキップできるのでは?という声をもらいましたので、検証しました。
538+
539+ テーブル定義だけ以下で、残りは同じです。
540+
541+ ``` sql テーブル定義
542+ CREATE TABLE t_order (
543+ item_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,
544+ unit_price NUMERIC NOT NULL , -- NOT NULL制約を追加
545+ quantity INT NOT NULL -- NOT NULL制約を追加
546+ );
547+ ```
548+
549+ | 検証パターン | 処理結果 |
550+ | :--- | :--- |
551+ | 1.格納生成列(NULL許容) | 9.0秒 |
552+ | 2.格納生成列(NOT NULL) | 25.5秒 |
553+ | 3.仮想生成列 (NULL許容) | 0.014秒 |
554+ | 4.仮想生成列(NOT NULL) | 4.9秒 |
555+
556+ 元列のNOT NULL制約無し版に比べ、少し早くなっていますが、実行の度に処理時間は変動するため気にしないでください。
557+
558+ 重要なのは、NOT NULL制約をつけても、4の結果は4.9秒かかっている(≒フルスキャンが発生していると推測できる)ことです。現状のPostgreSQLでは、元列にNOT NULL制約がついていていたとしても、生成列の「式」を確認して、この結果だとNOT NULLになりえないから、チェックは不要であると言った判定は行っていないと言えます。
559+
560+ ## 13. 生成列の定義変更はできるか
506561
507562[ ドキュメント] ( https://www.postgresql.org/docs/18/sql-altertable.html ) を読む限り、生成列の定義を直接変更することはできないように思えます(文法の読み取りが間違っていたらご指摘ください)。
508563
509564そのため、一度そのカラムを削除してから作り直すことになると思われます。例えば、先程の ` email_lower ` をいう検索専用の生成列を、さらに前後の空白をトリムする処理を追加します。
510565
511-
512566``` sql
513567-- (1) 既存の格納列を削除
514568ALTER TABLE m_user DROP COLUMN email_lower;
@@ -520,7 +574,7 @@ ADD COLUMN email_lower TEXT GENERATED ALWAYS AS (LOWER(TRIM(email))) STORED;
520574
521575流れ自体は仮想生成列でも同様です。格納生成列の場合は、(2)の処理でテーブルサイズによってはかなり時間がかかると思うので、注意が必要そうです(格納生成列のまま、瞬時に切り替える手順は今のところ、テーブル単位で新旧Verを作ってリネームする方法しか思いつきませんでした。また、格納生成列をDROP & ADDするということは、統計情報も消えるということなので、インデックス項目の場合はANALYZEもしたほうが良いでしょう)。
522576
523- ## 13 . 利用しているカラムをRENAME COLUMNしたらどうなるか
577+ ## 14 . 利用しているカラムをRENAME COLUMNしたらどうなるか
524578
525579生成列で利用しているカラムをリネームはできるのでしょうか?試してみます。
526580
@@ -569,7 +623,7 @@ Indexes:
569623
570624リネームにも追随してくれるの、気が効いていますね。賢い。
571625
572- ## 14 . 利用しているカラムをDROP COLUMNしたときどうなるか
626+ ## 15 . 利用しているカラムをDROP COLUMNしたときどうなるか
573627
574628格納生成列、仮想生成列それぞれで利用しているカラムを、DROPできるか試しました。
575629
0 commit comments