PostgreSQL 12 přichází se skvělou novou funkcí, Generated Columns. Funkce není úplně nová, ale standardizace, snadné použití, dostupnost a výkon byly v této nové verzi vylepšeny.
Generovaný sloupec je speciální sloupec v tabulce, který obsahuje data automaticky generovaná z jiných dat v řádku. Obsah vygenerovaného sloupce se automaticky vyplní a aktualizuje vždy, když se zdrojová data, jako jsou jakékoli jiné sloupce v řádku, sami změní.
Generované sloupce v PostgreSQL 12+
V posledních verzích PostgreSQL jsou generované sloupce vestavěnou funkcí, která umožňuje příkazům CREATE TABLE nebo ALTER TABLE přidat sloupec, ve kterém je obsah automaticky ‚generován‘ jako výsledek výrazu. Těmito výrazy mohou být jednoduché matematické operace z jiných sloupců nebo pokročilejší neměnná funkce. Mezi výhody implementace vygenerovaného sloupce do návrhu databáze patří:
- Možnost přidat sloupec do tabulky obsahující vypočítaná data bez nutnosti aktualizace kódu aplikace za účelem generování dat a jejich zahrnutí do operací INSERT a UPDATE.
- Zkrácení doby zpracování u extrémně častých příkazů SELECT, které by zpracovávaly data za běhu. Vzhledem k tomu, že zpracování dat probíhá v okamžiku INSERT nebo UPDATE, jsou data generována jednou a příkazy SELECT potřebují pouze data načíst. V těžkých čtecích prostředích to může být vhodnější, pokud je použito dodatečné úložiště dat přijatelné.
- Jelikož se vygenerované sloupce aktualizují automaticky při aktualizaci samotných zdrojových dat, přidání vygenerovaného sloupce přidá předpokládanou záruku, že data ve vygenerovaném sloupci jsou vždy správná.
V PostgreSQL 12 je k dispozici pouze generovaný sloupec typu „STORED“. V jiných databázových systémech je k dispozici vygenerovaný sloupec typu ‚VIRTUAL‘, který funguje spíše jako pohled, kde se výsledek počítá za běhu při načítání dat. Vzhledem k tomu, že funkce je tak podobná zobrazením a jednoduše zapisuje operaci do příkazu select, funkce není tak přínosná jako funkce „STORED“, o které se zde diskutuje, ale existuje šance, že budoucí verze budou tuto funkci zahrnovat.
Vytvoření tabulky s vygenerovaným sloupcem se provádí při definování samotného sloupce. V tomto příkladu je vygenerovaný sloupec „zisk“ a je automaticky generován odečtením kupní_ceny od sloupců prodejní_ceny a poté vynásobením sloupcem prodané množství.
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
);
V tomto příkladu je vytvořena tabulka „transakce“, která sleduje některé základní transakce a zisky imaginární kavárny. Vložením dat do této tabulky se zobrazí některé okamžité výsledky.
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
Při aktualizaci řádku se vygenerovaný sloupec automaticky aktualizuje:
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
To zajistí, že vygenerovaný sloupec bude vždy správný a na straně aplikace není potřeba žádná další logika.
POZNÁMKA:Vygenerované sloupce nelze VLOŽIT ani AKTUALIZOVAT přímo a jakýkoli pokus o to se vrátí jako CHYBA:
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.
Generované sloupce v PostgreSQL 11 a starších verzích
I když jsou vestavěné generované sloupce ve verzi 12 PostgreSQL novinkou, funkčně lze dosáhnout i v dřívějších verzích, jen to chce trochu více nastavení s uloženými procedurami a spouštěči. I když je však možné jej implementovat na starších verzích, kromě přidané funkčnosti, která může být přínosná, je obtížnější dosáhnout přísné shody vkládání dat a závisí na funkcích PL/pgSQL a programátorské vynalézavosti.
BONUS:Níže uvedený příklad bude fungovat také na PostgreSQL 12+, takže pokud je v novějších verzích potřebná nebo požadovaná přidaná funkce s kombinací funkce / spouštěče, je tato možnost platnou záložní možností a není omezena na pouze verze starší než 12.
I když toto je způsob, jak to udělat v předchozích verzích PostgreSQL, existuje několik dalších výhod této metody:
- Protože napodobování generovaného sloupce využívá funkci, lze použít složitější výpočty. Generované sloupce ve verzi 12 vyžadují operace IMMUTABLE, ale možnost spouštění/funkce by mohla používat funkci typu STABLE nebo VOLATILE s většími možnostmi a pravděpodobně i nižším výkonem.
- Použití funkce, která má možnost být STABLE nebo VOLATILE, také otevírá možnost AKTUALIZOVAT další sloupce, AKTUALIZOVAT další tabulky nebo dokonce vytvářet nová data pomocí VLOŽENÍ do jiných tabulek. (Nicméně, i když jsou tyto možnosti spouštění/funkcí mnohem flexibilnější, neznamená to, že chybí skutečný „Generovaný sloupec“, protože to, co je inzerováno, má vyšší výkon a efektivitu.)
V tomto příkladu je spouštěč / funkce nastavena tak, aby napodobovala funkce sloupce generovaného PostgreSQL 12+, spolu se dvěma kusy, které vyvolávají výjimku, pokud se INSERT nebo UPDATE pokusí vygenerovaný sloupec změnit. . Ty lze vynechat, ale pokud jsou vynechány, výjimky nebudou vyvolány a skutečná data VLOŽENÁ nebo AKTUALIZOVANÁ budou tiše zahozena, což by obecně nebylo doporučeno.
Samotný spouštěč je nastaven tak, aby se spustil BEFORE, což znamená, že ke zpracování dojde před skutečným vložením a vyžaduje RETURN of NEW, což je RECORD, který je upraven tak, aby obsahoval nově vygenerovanou hodnotu sloupce. Tento konkrétní příklad byl napsán pro běh na PostgreSQL verze 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();
POZNÁMKA:Ujistěte se, že funkce má správná oprávnění / vlastnictví, aby ji mohli spustit požadovaný uživatel aplikace.
Jak je vidět v předchozím příkladu, výsledky jsou stejné v předchozích verzích s řešením funkce/spouštěče:
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
Aktualizace dat bude podobná.
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
Nakonec, pokus VLOŽIT do nebo AKTUALIZOVAT samotný speciální sloupec bude mít za následek CHYBU:
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
V tomto příkladu se chová jinak než nastavení prvního vygenerovaného sloupce několika způsoby, které je třeba poznamenat:
- Pokud se pokusíte aktualizovat 'vygenerovaný sloupec', ale není nalezen žádný řádek, který by byl aktualizován, vrátí výsledek s výsledkem „UPDATE 0“, zatímco skutečný vygenerovaný sloupec ve verzi 12 bude stále vrátí ERROR, i když nebyl nalezen žádný řádek pro UPDATE.
- Při pokusu o aktualizaci sloupce zisku, který „by měl“ vždy vracet CHYBU, bude zadaná hodnota stejná jako správně „vygenerovaná“ hodnota. V konečném důsledku jsou však data správná, pokud však chcete vrátit CHYBU, pokud je sloupec zadán.
Dokumentace a komunita PostgreSQL
Oficiální dokumentace pro PostgreSQL Generated Columns se nachází na oficiálních webových stránkách PostgreSQL. Vraťte se, až budou vydány nové hlavní verze PostgreSQL, abyste objevili nové funkce, jakmile se objeví.
Zatímco generované sloupce v PostgreSQL 12 jsou poměrně přímočaré, implementace podobné funkce v předchozích verzích může být mnohem komplikovanější. Komunita PostgreSQL je velmi aktivní, masivní, celosvětová a vícejazyčná komunita, která se věnuje pomoci lidem jakékoli úrovně zkušeností s PostgreSQL řešit problémy a vytvářet nová řešení, jako je tento.
- IRC :Freenode má velmi aktivní kanál s názvem #postgres, kde si uživatelé navzájem pomáhají porozumět konceptům, opravovat chyby nebo hledat jiné zdroje. Úplný seznam dostupných kanálů freenode pro všechny věci PostgreSQL lze nalézt na webu PostgreSQL.org.
- Seznamy adresátů :PostgreSQL má několik e-mailových konferencí, ke kterým se lze připojit. Sem lze posílat otázky/problémy s delším formulářem a v kteroukoli dobu mohou oslovit mnohem více lidí než IRC. Seznamy lze nalézt na webu PostgreSQL a dobrými zdroji jsou seznamy pgsql-general nebo pgsql-admin.
- Prodleva :Komunitě PostgreSQL se také daří na Slacku a lze se k ní připojit na postgresteam.slack.com. Podobně jako IRC je k dispozici aktivní komunita, která odpovídá na otázky a zapojuje se do všeho, co se týká PostgreSQL.