PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Eine Übersicht über generierte Spalten für PostgreSQL

PostgreSQL 12 enthält eine großartige neue Funktion, generierte Spalten. Die Funktionalität ist nicht gerade neu, aber die Standardisierung, Benutzerfreundlichkeit, Zugänglichkeit und Leistung wurden in dieser neuen Version verbessert.

Eine generierte Spalte ist eine spezielle Spalte in einer Tabelle, die Daten enthält, die automatisch aus anderen Daten innerhalb der Zeile generiert wurden. Der Inhalt der generierten Spalte wird automatisch ausgefüllt und aktualisiert, wenn die Quelldaten, wie z. B. andere Spalten in der Zeile, selbst geändert werden.

Erzeugte Spalten in PostgreSQL 12+

In neueren Versionen von PostgreSQL sind generierte Spalten eine integrierte Funktion, die es den Anweisungen CREATE TABLE oder ALTER TABLE ermöglicht, eine Spalte hinzuzufügen, in der der Inhalt als Ergebnis eines Ausdrucks automatisch „generiert“ wird. Diese Ausdrücke können einfache mathematische Operationen aus anderen Spalten oder eine fortgeschrittenere unveränderliche Funktion sein. Einige Vorteile der Implementierung einer generierten Spalte in ein Datenbankdesign umfassen:

  • Die Fähigkeit, eine Spalte zu einer Tabelle hinzuzufügen, die berechnete Daten enthält, ohne dass der Anwendungscode aktualisiert werden muss, um die Daten zu generieren, um sie dann in INSERT- und UPDATE-Operationen aufzunehmen.
  • Verringerung der Verarbeitungszeit bei extrem häufigen SELECT-Anweisungen, die die Daten im laufenden Betrieb verarbeiten würden. Da die Verarbeitung der Daten zum Zeitpunkt von INSERT oder UPDATE erfolgt, werden die Daten einmal generiert und die SELECT-Anweisungen müssen die Daten nur abrufen. In Umgebungen mit vielen Lesevorgängen kann dies vorzuziehen sein, solange der verwendete zusätzliche Datenspeicher akzeptabel ist.
  • Da generierte Spalten automatisch aktualisiert werden, wenn die Quelldaten selbst aktualisiert werden, wird durch das Hinzufügen einer generierten Spalte eine angenommene Garantie hinzugefügt, dass die Daten in der generierten Spalte immer korrekt sind.

In PostgreSQL 12 ist nur der Typ „STORED“ der generierten Spalte verfügbar. In anderen Datenbanksystemen ist eine generierte Spalte vom Typ „VIRTUAL“ verfügbar, die eher wie eine Ansicht funktioniert, bei der das Ergebnis beim Abrufen der Daten im laufenden Betrieb berechnet wird. Da die Funktionalität Views so ähnlich ist und die Operation einfach in eine Select-Anweisung geschrieben wird, ist die Funktionalität nicht so vorteilhaft wie die hier besprochene „STORED“-Funktion, aber es besteht die Möglichkeit, dass zukünftige Versionen diese Funktion enthalten werden.

Das Erstellen einer Tabelle mit einer generierten Spalte erfolgt beim Definieren der Spalte selbst. In diesem Beispiel lautet die generierte Spalte „Profit“ und wird automatisch generiert, indem der „purchase_price“ von den „sale_price“-Spalten subtrahiert und dann mit der „quantity_sold“-Spalte multipliziert wird.

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

);

In diesem Beispiel wird eine Tabelle „Transaktionen“ erstellt, um einige grundlegende Transaktionen und Gewinne eines imaginären Cafés zu verfolgen. Das Einfügen von Daten in diese Tabelle zeigt einige sofortige Ergebnisse.

​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

Beim Aktualisieren der Zeile wird die generierte Spalte automatisch aktualisiert:

​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

Dadurch wird sichergestellt, dass die generierte Spalte immer korrekt ist, ohne dass auf der Anwendungsseite zusätzliche Logik erforderlich ist.

HINWEIS:Generierte Spalten können nicht direkt EINGESETZT oder AKTUALISIERT werden, und jeder Versuch, dies zu tun, führt zu einem ERROR:

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.

Erzeugte Spalten in PostgreSQL 11 und früher

Obwohl eingebaute generierte Spalten neu in Version 12 von PostgreSQL sind, kann die Funktionalität auch in früheren Versionen erreicht werden, es bedarf nur etwas mehr Setup mit gespeicherten Prozeduren und Triggern. Doch selbst mit der Möglichkeit, es in älteren Versionen zu implementieren, ist zusätzlich zu den zusätzlichen Funktionen, die vorteilhaft sein können, eine strikte Einhaltung der Dateneingabe schwieriger zu erreichen und hängt von PL/pgSQL-Funktionen und Programmiereinfallsreichtum ab.

BONUS:Das folgende Beispiel funktioniert auch auf PostgreSQL 12+, wenn also die zusätzliche Funktionalität mit einer Funktion/Trigger-Kombination in neueren Versionen benötigt oder gewünscht wird, ist diese Option ein gültiges Fallback und nicht darauf beschränkt nur Versionen älter als 12. 

Während dies in früheren Versionen von PostgreSQL eine Möglichkeit ist, bietet diese Methode einige zusätzliche Vorteile: 

  • Da das Nachahmen der generierten Spalte eine Funktion verwendet, können komplexere Berechnungen verwendet werden. Generierte Spalten in Version 12 erfordern IMMUTABLE-Operationen, aber eine Trigger-/Funktionsoption könnte einen STABLE- oder VOLATILE-Funktionstyp mit größeren Möglichkeiten und wahrscheinlich entsprechend geringerer Leistung verwenden.
  • Die Verwendung einer Funktion, die die Option hat, STABLE oder VOLATILE zu sein, eröffnet auch die Möglichkeit, zusätzliche Spalten zu AKTUALISIEREN, andere Tabellen zu AKTUALISIEREN oder sogar neue Daten über EINFÜGE in andere Tabellen zu erstellen. (Obwohl diese Trigger-/Funktionsoptionen viel flexibler sind, heißt das nicht, dass es an einer tatsächlichen „generierten Spalte“ mangelt, da sie das tut, was beworben wird, mit größerer Leistung und Effizienz.)

In diesem Beispiel wird ein Trigger/eine Funktion eingerichtet, um die Funktionalität einer von PostgreSQL 12+ generierten Spalte nachzuahmen, zusammen mit zwei Teilen, die eine Ausnahme auslösen, wenn ein INSERT oder UPDATE versucht, die generierte Spalte zu ändern . Diese können weggelassen werden, aber wenn sie weggelassen werden, werden keine Ausnahmen ausgelöst, und die eigentlichen Daten, die INSERTed oder UPDATEd sind, werden stillschweigend verworfen, was im Allgemeinen nicht empfohlen wird.

Der Trigger selbst ist so eingestellt, dass er BEFORE ausgeführt wird, was bedeutet, dass die Verarbeitung vor dem eigentlichen Einfügen stattfindet und die RETURN of NEW erfordert, was der RECORD ist, der so modifiziert wird, dass er den neu generierten Spaltenwert enthält. Dieses spezifische Beispiel wurde geschrieben, um auf PostgreSQL Version 11 ausgeführt zu werden.

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();

HINWEIS:Stellen Sie sicher, dass die Funktion über die richtigen Berechtigungen/Besitzrechte verfügt, um von den gewünschten Anwendungsbenutzern ausgeführt zu werden.

Wie im vorherigen Beispiel zu sehen, sind die Ergebnisse in früheren Versionen mit einer Funktion/Trigger-Lösung gleich:

​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

Das Aktualisieren der Daten wird ähnlich sein.

​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

Zu guter Letzt führt der Versuch, die spezielle Spalte selbst einzufügen oder zu aktualisieren, zu einem ERROR:

​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

In diesem Beispiel verhält es sich in einigen wichtigen Punkten anders als das erste generierte Spalten-Setup:

  • Wenn versucht wird, die „generierte Spalte“ zu aktualisieren, aber keine zu aktualisierende Zeile gefunden wird, wird ein „UPDATE 0“-Ergebnis als Erfolg zurückgegeben, während eine tatsächliche generierte Spalte in Version 12 dies immer noch tut gibt einen ERROR zurück, auch wenn keine Zeile zum UPDATE gefunden wird.
  • Wenn Sie versuchen, die Gewinnspalte zu aktualisieren, die immer einen ERROR zurückgeben „sollte“, wenn der angegebene Wert mit dem korrekt „erzeugten“ Wert übereinstimmt, wird es erfolgreich sein. Letztendlich sind die Daten aber korrekt, wenn bei Angabe der Spalte ein ERROR zurückgegeben werden soll.

Dokumentation und PostgreSQL-Community

Die offizielle Dokumentation für die von PostgreSQL generierten Spalten befindet sich auf der offiziellen PostgreSQL-Website. Schauen Sie immer wieder vorbei, wenn neue Hauptversionen von PostgreSQL veröffentlicht werden, um neue Funktionen zu entdecken, sobald sie erscheinen.

Während generierte Spalten in PostgreSQL 12 ziemlich einfach sind, kann die Implementierung ähnlicher Funktionen in früheren Versionen viel komplizierter werden. Die PostgreSQL-Community ist eine sehr aktive, massive, weltweite und mehrsprachige Community, die sich zum Ziel gesetzt hat, Menschen aller PostgreSQL-Erfahrungsstufen bei der Lösung von Problemen und der Entwicklung neuer Lösungen wie dieser zu helfen.

  • IRC :Freenode hat einen sehr aktiven Kanal namens #postgres, in dem Benutzer sich gegenseitig helfen, Konzepte zu verstehen, Fehler zu beheben oder andere Ressourcen zu finden. Eine vollständige Liste der verfügbaren Freenode-Kanäle rund um PostgreSQL finden Sie auf der Website PostgreSQL.org.
  • Mailinglisten :PostgreSQL hat eine Handvoll Mailinglisten, denen man beitreten kann. Längere Fragen / Probleme können hier gesendet werden und können zu jedem Zeitpunkt viel mehr Menschen als IRC erreichen. Die Listen finden Sie auf der PostgreSQL-Website, und die Listen pgsql-general oder pgsql-admin sind gute Ressourcen.
  • Slack :Die PostgreSQL-Community blüht auch auf Slack auf und kann unter postgresteam.slack.com beitreten. Ähnlich wie beim IRC steht eine aktive Community zur Verfügung, um Fragen zu beantworten und sich in allen Dingen rund um PostgreSQL zu engagieren.