sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQL用に生成された列の概要

    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生成列の公式ドキュメントは、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のすべてに取り組むことができます。

    1. クラスタ化されたインデックスはどの列に配置する必要がありますか?

    2. AWSにMySQLGaleraクラスターをデプロイする簡単な方法

    3. OracleFormsのクエリで表示項目の値を入力する

    4. PostgreSQL:ASが存在しない場合はテーブルを作成します