PostgreSQL 12には、優れた新機能である生成列が付属しています。機能はまったく新しいものではありませんが、この新しいバージョンでは、標準化、使いやすさ、アクセシビリティ、パフォーマンスが改善されています。
生成された列は、行内の他のデータから自動的に生成されたデータを含むテーブル内の特別な列です。生成された列のコンテンツは、行の他の列などのソースデータが変更されるたびに自動的に入力され、更新されます。
PostgreSQL12以降で生成された列
PostgreSQLの最近のバージョンでは、生成された列は組み込み機能であり、CREATETABLEまたはALTERTABLEステートメントで、式の結果としてコンテンツが自動的に「生成」される列を追加できます。これらの式は、他の列からの単純な数学演算、またはより高度な不変関数である可能性があります。生成された列をデータベース設計に実装することのいくつかの利点は次のとおりです。
- アプリケーションコードを更新してデータを生成し、それをINSERTおよびUPDATE操作に含めることなく、計算されたデータを含むテーブルに列を追加する機能。
- データをオンザフライで処理する、非常に頻繁なSELECTステートメントの処理時間を短縮します。データの処理はINSERTまたはUPDATE時に行われるため、データは1回生成され、SELECTステートメントはデータを取得するだけで済みます。大量の読み取り環境では、使用される追加のデータストレージが許容できる限り、これが望ましい場合があります。
- ソースデータ自体が更新されると、生成された列が自動的に更新されるため、生成された列を追加すると、生成された列のデータが常に正しいという想定された保証が追加されます。
PostgreSQL 12では、「STORED」タイプの生成された列のみが使用可能です。他のデータベースシステムでは、「VIRTUAL」タイプの生成された列を使用できます。これは、データが取得されたときに結果がオンザフライで計算されるビューのように機能します。機能はビューに非常に似ており、操作をselectステートメントに書き込むだけなので、この機能はここで説明する「STORED」機能ほど有益ではありませんが、将来のバージョンにこの機能が含まれる可能性があります。
生成された列を含むテーブルの作成は、列自体を定義するときに行われます。この例では、生成された列は「profit」であり、sale_price列からpurchase_priceを減算し、quantity_sold列を乗算することによって自動的に生成されます。
CREATE TABLE public.transactions (
transactions_sid serial primary key,
transaction_date timestamp with time zone DEFAULT now() NOT NULL,
product_name character varying NOT NULL,
purchase_price double precision NOT NULL,
sale_price double precision NOT NULL,
quantity_sold integer NOT NULL,
profit double precision NOT NULL GENERATED ALWAYS AS ((sale_price - purchase_price) * quantity_sold) STORED
);
この例では、架空のコーヒーショップの基本的なトランザクションと利益を追跡するために、「トランザクション」テーブルが作成されています。このテーブルにデータを挿入すると、すぐに結果が表示されます。
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('House Blend Coffee', 5, 11.99, 1);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('French Roast Coffee', 6, 12.99, 4);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('BULK: House Blend Coffee, 10LB', 40, 100, 6);
severalnines=# SELECT * FROM public.transactions;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
1 | 2020-02-28 04:50:06.626371+00 | House Blend Coffee | 5 | 11.99 | 1 | 6.99
2 | 2020-02-28 04:50:53.313572+00 | French Roast Coffee | 6 | 12.99 | 4 | 27.96
3 | 2020-02-28 04:51:08.531875+00 | BULK: House Blend Coffee, 10LB | 40 | 100 | 6 | 360
行を更新すると、生成された列が自動的に更新されます:
severalnines=# UPDATE public.transactions SET sale_price = 95 WHERE transactions_sid = 3;
UPDATE 1
severalnines=# SELECT * FROM public.transactions WHERE transactions_sid = 3;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
3 | 2020-02-28 05:55:11.233077+00 | BULK: House Blend Coffee, 10LB | 40 | 95 | 6 | 330
これにより、アプリケーション側で追加のロジックを必要とせずに、生成された列が常に正しいことが保証されます。
注:生成された列を直接挿入または更新することはできません。そうしようとすると、エラーが返されます:
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold, profit) VALUES ('BULK: House Blend Coffee, 10LB', 40, 95, 1, 95);
ERROR: cannot insert into column "profit"
DETAIL: Column "profit" is a generated column.
severalnines=# UPDATE public.transactions SET profit = 330 WHERE transactions_sid = 3;
ERROR: column "profit" can only be updated to DEFAULT
DETAIL: Column "profit" is a generated column.
PostgreSQL11以前で生成された列
組み込みの生成された列はPostgreSQLのバージョン12の新機能ですが、以前のバージョンでも機能を実現できます。ストアドプロシージャとトリガーを使用してもう少しセットアップする必要があります。ただし、古いバージョンに実装する機能があっても、有益な機能が追加されていることに加えて、厳密なデータ入力コンプライアンスを実現するのは難しく、PL/pgSQLの機能とプログラミングの工夫に依存しています。
ボーナス:以下の例はPostgreSQL 12以降でも機能するため、新しいバージョンで関数/トリガーコンボを備えた追加機能が必要または必要な場合、このオプションは有効なフォールバックであり、以下に制限されません。 12より古いバージョンのみ。
これは以前のバージョンのPostgreSQLで行う方法ですが、この方法にはいくつかの追加の利点があります。
- 生成された列を模倣すると関数が使用されるため、より複雑な計算を使用できます。バージョン12で生成された列には、IMMUTABLE操作が必要ですが、トリガー/関数オプションは、STABLEまたはVOLATILEタイプの関数を使用する可能性があり、それに応じてパフォーマンスが低下する可能性があります。
- STABLEまたはVOLATILEのオプションがある関数を使用すると、追加の列を更新したり、他のテーブルを更新したり、INSERTSを介して他のテーブルに新しいデータを作成したりすることもできます。 (ただし、これらのトリガー/関数オプションははるかに柔軟性がありますが、実際の「生成された列」が不足しているわけではありません。これは、より優れたパフォーマンスと効率で宣伝されているものです。)
この例では、PostgreSQL 12+で生成された列の機能を模倣するようにトリガー/関数が設定され、INSERTまたはUPDATEで生成された列を変更しようとすると例外が発生します。 。これらは省略できますが、省略した場合、例外は発生せず、挿入または更新された実際のデータは静かに破棄されます。これは通常、お勧めできません。
トリガー自体はBEFOREを実行するように設定されています。つまり、実際の挿入が行われる前に処理が行われ、新しく生成された列値を含むように変更されたRECORDであるRETURNが必要です。この特定の例は、PostgreSQLバージョン11で実行するように作成されています。
CREATE TABLE public.transactions (
transactions_sid serial primary key,
transaction_date timestamp with time zone DEFAULT now() NOT NULL,
product_name character varying NOT NULL,
purchase_price double precision NOT NULL,
sale_price double precision NOT NULL,
quantity_sold integer NOT NULL,
profit double precision NOT NULL
);
CREATE OR REPLACE FUNCTION public.generated_column_function()
RETURNS trigger
LANGUAGE plpgsql
IMMUTABLE
AS $function$
BEGIN
-- This statement mimics the ERROR on built in generated columns to refuse INSERTS on the column and return an ERROR.
IF (TG_OP = 'INSERT') THEN
IF (NEW.profit IS NOT NULL) THEN
RAISE EXCEPTION 'ERROR: cannot insert into column "profit"' USING DETAIL = 'Column "profit" is a generated column.';
END IF;
END IF;
-- This statement mimics the ERROR on built in generated columns to refuse UPDATES on the column and return an ERROR.
IF (TG_OP = 'UPDATE') THEN
-- Below, IS DISTINCT FROM is used because it treats nulls like an ordinary value.
IF (NEW.profit::VARCHAR IS DISTINCT FROM OLD.profit::VARCHAR) THEN
RAISE EXCEPTION 'ERROR: cannot update column "profit"' USING DETAIL = 'Column "profit" is a generated column.';
END IF;
END IF;
NEW.profit := ((NEW.sale_price - NEW.purchase_price) * NEW.quantity_sold);
RETURN NEW;
END;
$function$;
CREATE TRIGGER generated_column_trigger BEFORE INSERT OR UPDATE ON public.transactions FOR EACH ROW EXECUTE PROCEDURE public.generated_column_function();
注:関数に、目的のアプリケーションユーザーが実行するための正しい権限/所有権があることを確認してください。
前の例で見たように、結果は関数/トリガーソリューションを使用した以前のバージョンと同じです:
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('House Blend Coffee', 5, 11.99, 1);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('French Roast Coffee', 6, 12.99, 4);
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('BULK: House Blend Coffee, 10LB', 40, 100, 6);
severalnines=# SELECT * FROM public.transactions;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
1 | 2020-02-28 00:35:14.855511-07 | House Blend Coffee | 5 | 11.99 | 1 | 6.99
2 | 2020-02-28 00:35:21.764449-07 | French Roast Coffee | 6 | 12.99 | 4 | 27.96
3 | 2020-02-28 00:35:27.708761-07 | BULK: House Blend Coffee, 10LB | 40 | 100 | 6 | 360
severalnines=# UPDATE public.transactions SET sale_price = 95 WHERE transactions_sid = 3;
UPDATE 1
severalnines=# SELECT * FROM public.transactions WHERE transactions_sid = 3;
transactions_sid | transaction_date | product_name | purchase_price | sale_price | quantity_sold | profit
------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------
3 | 2020-02-28 00:48:52.464344-07 | BULK: House Blend Coffee, 10LB | 40 | 95 | 6 | 330
最後に、特別な列自体にINSERTまたはUPDATEを実行しようとすると、エラーが発生します:
severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold, profit) VALUES ('BULK: House Blend Coffee, 10LB', 40, 95, 1, 95);
ERROR: ERROR: cannot insert into column "profit"
DETAIL: Column "profit" is a generated column.
CONTEXT: PL/pgSQL function generated_column_function() line 7 at RAISE
severalnines=# UPDATE public.transactions SET profit = 3030 WHERE transactions_sid = 3;
ERROR: ERROR: cannot update column "profit"
DETAIL: Column "profit" is a generated column.
CONTEXT: PL/pgSQL function generated_column_function() line 15 at RAISE
この例では、最初に生成された列の設定とは、注意すべきいくつかの点で異なる動作をします。
- 「生成された列」を更新しようとしたが、更新された行が見つからなかった場合、「UPDATE 0」の結果で成功を返しますが、バージョン12の実際の生成された列は引き続きUPDATEする行が見つからない場合でも、エラーを返します。
- 常にエラーを返す「はず」の利益列を更新しようとすると、指定された値が正しく「生成された」値と同じであれば、成功します。最終的にはデータは正しいですが、列が指定されている場合にエラーを返したい場合は、データは正しいです。
PostgreSQL生成列の公式ドキュメントは、PostgreSQLの公式Webサイトにあります。 PostgreSQLの新しいメジャーバージョンがリリースされたときにもう一度確認して、新しい機能が表示されたときに発見してください。
PostgreSQL 12で生成された列はかなり単純ですが、以前のバージョンで同様の機能を実装すると、はるかに複雑になる可能性があります。 PostgreSQLコミュニティは、あらゆるレベルのPostgreSQLの経験を持つ人々が問題を解決し、このような新しいソリューションを作成するのを支援することを目的とした、非常に活発で大規模な世界規模の多言語コミュニティです。
- IRC :Freenodeには#postgresと呼ばれる非常にアクティブなチャネルがあり、ユーザーはお互いに概念を理解したり、エラーを修正したり、他のリソースを見つけたりするのに役立ちます。 PostgreSQLで利用可能なすべてのフリーノードチャネルの完全なリストは、PostgreSQL.orgのWebサイトにあります。
- メーリングリスト :PostgreSQLには、参加できるメーリングリストがいくつかあります。より長い形式の質問/問題をここに送信することができ、いつでもIRCよりもはるかに多くの人々に到達することができます。リストはPostgreSQLWebサイトにあり、pgsql-generalまたはpgsql-adminのリストは優れたリソースです。
- たるみ :PostgreSQLコミュニティもSlackで繁栄しており、postgresteam.slack.comから参加できます。 IRCと同様に、アクティブなコミュニティを利用して質問に回答し、PostgreSQLのすべてに取り組むことができます。