sql >> Database >  >> RDS >> PostgreSQL

Een overzicht van gegenereerde kolommen voor PostgreSQL

PostgreSQL 12 wordt geleverd met een geweldige nieuwe functie, Generated Columns. De functionaliteit is niet bepaald nieuw, maar de standaardisatie, het gebruiksgemak, de toegankelijkheid en de prestaties zijn in deze nieuwe versie verbeterd.

Een gegenereerde kolom is een speciale kolom in een tabel die gegevens bevat die automatisch zijn gegenereerd op basis van andere gegevens in de rij. De inhoud van de gegenereerde kolom wordt automatisch ingevuld en bijgewerkt wanneer de brongegevens, zoals andere kolommen in de rij, zelf worden gewijzigd.

Gegenereerde kolommen in PostgreSQL 12+

In recente versies van PostgreSQL zijn gegenereerde kolommen een ingebouwde functie waarmee de CREATE TABLE- of ALTER TABLE-instructies een kolom kunnen toevoegen waarin de inhoud automatisch wordt 'gegenereerd' als resultaat van een expressie. Deze uitdrukkingen kunnen eenvoudige wiskundige bewerkingen uit andere kolommen zijn, of een meer geavanceerde onveranderlijke functie. Enkele voordelen van het implementeren van een gegenereerde kolom in een databaseontwerp zijn:

  • De mogelijkheid om een ​​kolom toe te voegen aan een tabel met berekende gegevens zonder dat de applicatiecode hoeft te worden bijgewerkt om de gegevens te genereren om deze vervolgens op te nemen in INSERT- en UPDATE-bewerkingen.
  • Verkorting van de verwerkingstijd voor extreem frequente SELECT-instructies die de gegevens on-the-fly zouden verwerken. Aangezien de verwerking van de gegevens plaatsvindt op het moment van INSERT of UPDATE, worden de gegevens één keer gegenereerd en hoeven de SELECT-instructies alleen de gegevens op te halen. In zware leesomgevingen kan dit de voorkeur hebben, zolang de extra dataopslag die wordt gebruikt acceptabel is.
  • Aangezien gegenereerde kolommen automatisch worden bijgewerkt wanneer de brongegevens zelf worden bijgewerkt, voegt het toevoegen van een gegenereerde kolom een ​​veronderstelde garantie toe dat de gegevens in de gegenereerde kolom altijd correct zijn.

In PostgreSQL 12 is alleen het type 'OPGESLAGEN' van de gegenereerde kolom beschikbaar. In andere databasesystemen is een gegenereerde kolom met het type 'VIRTUAL' beschikbaar, die meer werkt als een weergave waarin het resultaat direct wordt berekend wanneer de gegevens worden opgehaald. Omdat de functionaliteit zo lijkt op views en de bewerking simpelweg in een select-statement schrijft, is de functionaliteit niet zo gunstig als de 'OPGESLAGEN'-functionaliteit die hier wordt besproken, maar er is een kans dat toekomstige versies de functie zullen bevatten.

Een tabel maken met een gegenereerde kolom wordt gedaan bij het definiëren van de kolom zelf. In dit voorbeeld is de gegenereerde kolom 'winst' en wordt automatisch gegenereerd door de aankoopprijs af te trekken van de verkoopprijskolommen en vervolgens te vermenigvuldigen met de hoeveelheid verkochte kolom.

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 dit voorbeeld wordt een 'transacties'-tabel gemaakt om enkele basistransacties en winsten van een denkbeeldige coffeeshop bij te houden. Als u gegevens in deze tabel invoegt, worden enkele onmiddellijke resultaten weergegeven.

​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

Wanneer de rij wordt bijgewerkt, wordt de gegenereerde kolom automatisch bijgewerkt:

​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

Dit zorgt ervoor dat de gegenereerde kolom altijd correct is, zonder dat er extra logica nodig is aan de applicatiekant.

OPMERKING:Gegenereerde kolommen kunnen niet rechtstreeks worden INGEVOERD in of BIJGEWERKT, en elke poging om dit te doen zal terugkeren in een FOUT:

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.

Gegenereerde kolommen op PostgreSQL 11 en eerder

Hoewel ingebouwde gegenereerde kolommen nieuw zijn in versie 12 van PostgreSQL, kan het functioneel nog steeds worden bereikt in eerdere versies, het heeft alleen wat meer instellingen nodig met opgeslagen procedures en triggers. Maar zelfs met de mogelijkheid om het op oudere versies te implementeren, naast de toegevoegde functionaliteit die nuttig kan zijn, is strikte naleving van de gegevensinvoer moeilijker te bereiken en hangt af van PL/pgSQL-functies en programmeerinventiviteit.

BONUS:het onderstaande voorbeeld werkt ook op PostgreSQL 12+, dus als de toegevoegde functionaliteit met een functie/triggercombinatie nodig of gewenst is in nieuwere versies, is deze optie een geldige fallback en niet beperkt tot alleen versies ouder dan 12. 

Hoewel dit een manier is om dit te doen in eerdere versies van PostgreSQL, zijn er een aantal extra voordelen van deze methode: 

  • Omdat het nabootsen van de gegenereerde kolom een ​​functie gebruikt, kunnen complexere berekeningen worden gebruikt. Gegenereerde kolommen in versie 12 vereisen IMMUTABLE-bewerkingen, maar een trigger / functie-optie kan een STABIEL of VLUCHTIG type functie gebruiken met meer mogelijkheden en waarschijnlijk mindere prestaties dienovereenkomstig.
  • Het gebruik van een functie die de optie STABIEL of VOLATIEL heeft, opent ook de mogelijkheid om extra kolommen BIJWERKEN, andere tabellen BIJWERKEN, of zelfs nieuwe gegevens maken via INSERTS in andere tabellen. (Hoewel deze trigger-/functie-opties veel flexibeler zijn, wil dat niet zeggen dat er geen echte "gegenereerde kolom" ontbreekt, omdat deze doet wat wordt geadverteerd met betere prestaties en efficiëntie.)

In dit voorbeeld is een trigger / functie ingesteld om de functionaliteit van een door PostgreSQL 12+ gegenereerde kolom na te bootsen, samen met twee stukken die een uitzondering veroorzaken als een INSERT of UPDATE probeert de gegenereerde kolom te wijzigen . Deze kunnen worden weggelaten, maar als ze worden weggelaten, worden er geen uitzonderingen gemaakt en worden de daadwerkelijke gegevens INSERTED of UPDATEd stilletjes weggegooid, wat over het algemeen niet wordt aanbevolen.

De trigger zelf is ingesteld om BEFORE uit te voeren, wat betekent dat de verwerking plaatsvindt voordat de daadwerkelijke invoeging plaatsvindt, en de RETURN van NEW vereist, wat het RECORD is dat is gewijzigd om de nieuw gegenereerde kolomwaarde te bevatten. Dit specifieke voorbeeld is geschreven om op PostgreSQL versie 11 te draaien.

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

OPMERKING:Zorg ervoor dat de functie de juiste rechten/eigendom heeft om te worden uitgevoerd door de gewenste applicatiegebruiker(s).

Zoals te zien is in het vorige voorbeeld, zijn de resultaten hetzelfde in eerdere versies met een functie / trigger-oplossing:

​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

Het bijwerken van de gegevens is vergelijkbaar.

​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

Ten slotte, zal een poging om de speciale kolom zelf INVOEREN of BIJWERKEN, resulteren in een FOUT:

​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 dit voorbeeld werkt het op een aantal manieren anders dan de eerste gegenereerde kolomconfiguratie:

  • Als geprobeerd wordt om de 'gegenereerde kolom' te updaten, maar er wordt geen rij gevonden die is bijgewerkt, zal dit succes opleveren met het resultaat 'UPDATE 0', terwijl een daadwerkelijke gegenereerde kolom in versie 12 nog steeds retourneer een FOUT, zelfs als er geen rij is gevonden om te BIJWERKEN.
  • Bij een poging om de winstkolom bij te werken, die altijd een FOUT moet retourneren, zal dit lukken als de opgegeven waarde hetzelfde is als de correct 'gegenereerde' waarde. Uiteindelijk zijn de gegevens echter correct als de wens is om een ​​FOUT te retourneren als de kolom is opgegeven.

Documentatie en PostgreSQL-gemeenschap

De officiële documentatie voor de door PostgreSQL gegenereerde kolommen is te vinden op de officiële PostgreSQL-website. Kom terug wanneer nieuwe belangrijke versies van PostgreSQL worden uitgebracht om nieuwe functies te ontdekken wanneer ze verschijnen.

Hoewel gegenereerde kolommen in PostgreSQL 12 vrij eenvoudig zijn, kan het implementeren van vergelijkbare functionaliteit in eerdere versies veel gecompliceerder worden. De PostgreSQL-gemeenschap is een zeer actieve, omvangrijke, wereldwijde en meertalige gemeenschap die zich toelegt op het helpen van mensen van elk niveau van PostgreSQL-ervaring bij het oplossen van problemen en het creëren van nieuwe oplossingen zoals deze.

  • IRC :Freenode heeft een zeer actief kanaal genaamd #postgres, waar gebruikers elkaar helpen concepten te begrijpen, fouten op te lossen of andere bronnen te vinden. Een volledige lijst met beschikbare freenode-kanalen voor alles wat met PostgreSQL te maken heeft, is te vinden op de PostgreSQL.org-website.
  • Mailinglijsten :PostgreSQL heeft een handvol mailinglijsten waaraan u kunt deelnemen. Vragen / problemen met een langere vorm kunnen hier worden verzonden en kunnen op elk moment veel meer mensen bereiken dan IRC. De lijsten zijn te vinden op de PostgreSQL-website en de lijsten pgsql-general of pgsql-admin zijn goede bronnen.
  • Slapheid :De PostgreSQL-gemeenschap bloeit ook op Slack en kan worden toegevoegd via postgresteam.slack.com. Net als IRC is er een actieve gemeenschap beschikbaar om vragen te beantwoorden en deel te nemen aan alles wat met PostgreSQL te maken heeft.

  1. Een overzicht van Amazon RDS &Aurora-aanbiedingen voor PostgreSQL

  2. Basisklasse en afgeleide objectvarianten

  3. SQL:Wat is beter een bit of een char(1)

  4. DATEADD() Voorbeelden in SQL Server