PostgreSQL 12 zawiera wspaniałą nową funkcję, Wygenerowane kolumny. Funkcjonalność nie jest niczym nowym, ale w tej nowej wersji poprawiono standaryzację, łatwość użytkowania, dostępność i wydajność.
Wygenerowana kolumna to specjalna kolumna w tabeli, która zawiera dane automatycznie wygenerowane z innych danych w wierszu. Zawartość wygenerowanej kolumny jest automatycznie wypełniana i aktualizowana za każdym razem, gdy dane źródłowe, takie jak inne kolumny w wierszu, same się zmienią.
Generowane kolumny w PostgreSQL 12+
W najnowszych wersjach PostgreSQL, wygenerowane kolumny są wbudowaną funkcją pozwalającą instrukcji CREATE TABLE lub ALTER TABLE na dodanie kolumny, w której treść jest automatycznie „generowana” w wyniku wyrażenia. Wyrażenia te mogą być prostymi operacjami matematycznymi z innych kolumn lub bardziej zaawansowaną funkcją niezmienną. Niektóre korzyści z implementacji wygenerowanej kolumny do projektu bazy danych obejmują:
- Możliwość dodania kolumny do tabeli zawierającej obliczone dane bez konieczności aktualizowania kodu aplikacji w celu wygenerowania danych, aby następnie uwzględnić je w operacjach INSERT i UPDATE.
- Skrócenie czasu przetwarzania w przypadku niezwykle częstych instrukcji SELECT, które przetwarzałyby dane w locie. Ponieważ przetwarzanie danych odbywa się w momencie INSERT lub UPDATE, dane są generowane raz, a instrukcje SELECT muszą tylko pobierać dane. W środowiskach o dużym natężeniu odczytu może to być preferowane, o ile używana dodatkowa pamięć masowa jest akceptowalna.
- Ponieważ wygenerowane kolumny są aktualizowane automatycznie, gdy aktualizowane są same dane źródłowe, dodanie wygenerowanej kolumny doda zakładaną gwarancję, że dane w wygenerowanej kolumnie są zawsze poprawne.
W PostgreSQL 12 dostępny jest tylko typ wygenerowanej kolumny „STORED”. W innych systemach bazodanowych dostępna jest wygenerowana kolumna typu „VIRTUAL”, która działa bardziej jak widok, w którym wynik jest obliczany w locie, gdy dane są pobierane. Ponieważ funkcjonalność jest tak podobna do widoków i po prostu zapisuje operację w instrukcji select, nie jest ona tak korzystna, jak omawiana tutaj funkcja „ZACHOWANE”, ale istnieje szansa, że przyszłe wersje będą zawierać tę funkcję.
Tworzenie tabeli z wygenerowaną kolumną jest wykonywane podczas definiowania samej kolumny. W tym przykładzie generowana kolumna to „zysk” i jest generowana automatycznie przez odjęcie ceny zakupu z kolumn cena_sprzedaży, a następnie pomnożenie przez kolumnę ilość_sprzedaży.
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
);
W tym przykładzie tworzona jest tabela „transakcje” w celu śledzenia podstawowych transakcji i zysków wyimaginowanej kawiarni. Wstawienie danych do tej tabeli spowoduje natychmiastowe wyświetlenie wyników.
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
Podczas aktualizacji wiersza wygenerowana kolumna zostanie automatycznie zaktualizowana:
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
Zapewni to, że wygenerowana kolumna będzie zawsze poprawna, bez dodatkowej logiki po stronie aplikacji.
UWAGA:Wygenerowanych kolumn nie można bezpośrednio WSTAWIĆ ani UAKTUALNIĆ, a każda próba wykonania tego zadania zwróci BŁĄD:
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.
Wygenerowane kolumny w PostgreSQL 11 i wcześniejszych
Mimo że wbudowane generowane kolumny są nowością w wersji 12 PostgreSQL, funkcjonalność nadal można osiągnąć we wcześniejszych wersjach, wymaga ona tylko nieco większej konfiguracji z procedurami składowanymi i wyzwalaczami. Jednak nawet przy możliwości wdrożenia go w starszych wersjach, oprócz dodanej funkcjonalności, która może być korzystna, ścisła zgodność danych wejściowych jest trudniejsza do osiągnięcia i zależy od funkcji PL/pgSQL i pomysłowości programistycznej.
BONUS:Poniższy przykład będzie działał również na PostgreSQL 12+, więc jeśli dodana funkcjonalność z kombinacją funkcji / wyzwalacza jest potrzebna lub pożądana w nowszych wersjach, ta opcja jest poprawnym rozwiązaniem awaryjnym i nie jest ograniczona do tylko wersje starsze niż 12.
Chociaż jest to sposób na zrobienie tego w poprzednich wersjach PostgreSQL, istnieje kilka dodatkowych zalet tej metody:
- Ponieważ naśladowanie wygenerowanej kolumny wykorzystuje funkcję, można zastosować bardziej złożone obliczenia. Wygenerowane kolumny w wersji 12 wymagają operacji IMMUTABLE, ale opcja wyzwalacza / funkcji może używać funkcji typu STABILNA lub NIELOTNA z większymi możliwościami i prawdopodobnie mniejszą wydajnością.
- Korzystanie z funkcji, która może być STABILNA lub NIESTABILNA, otwiera również możliwość AKTUALIZACJI dodatkowych kolumn, AKTUALIZACJI innych tabel, a nawet tworzenia nowych danych poprzez WSTAWKI do innych tabel. (Jednakże te opcje wyzwalacza / funkcji są znacznie bardziej elastyczne, nie oznacza to, że brakuje rzeczywistej „wygenerowanej kolumny”, ponieważ ma ona to, co jest reklamowane z większą wydajnością i wydajnością).
W tym przykładzie wyzwalacz / funkcja jest skonfigurowana tak, aby naśladować funkcjonalność kolumny generowanej w PostgreSQL 12+, wraz z dwoma elementami, które zgłaszają wyjątek, jeśli próba zmiany wygenerowanej kolumny INSERT lub UPDATE . Można je pominąć, ale jeśli zostaną pominięte, wyjątki nie zostaną zgłoszone, a rzeczywiste dane wstawione lub zaktualizowane zostaną po cichu odrzucone, co generalnie nie byłoby zalecane.
Sam wyzwalacz jest ustawiony na uruchomienie PRZED, co oznacza, że przetwarzanie odbywa się przed faktycznym wstawieniem i wymaga RETURN of NEW, który jest REKORDEM zmodyfikowanym tak, aby zawierał nową wygenerowaną wartość kolumny. Ten konkretny przykład został napisany do działania w PostgreSQL w wersji 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();
UWAGA:Upewnij się, że funkcja ma odpowiednie uprawnienia/własność do wykonania przez żądanych użytkowników aplikacji.
Jak widać w poprzednim przykładzie, wyniki są takie same w poprzednich wersjach z rozwiązaniem funkcji / wyzwalacza:
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
Aktualizacja danych będzie podobna.
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
Na koniec próba WSTAWIENIA lub AKTUALIZACJI samej kolumny specjalnej spowoduje wystąpienie BŁĘDU:
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
W tym przykładzie działa inaczej niż pierwsza wygenerowana konfiguracja kolumny na kilka sposobów, na które należy zwrócić uwagę:
- Jeśli próbuje się zaktualizować „wygenerowaną kolumnę”, ale nie znaleziono żadnego wiersza do zaktualizowania, zwróci ona sukces z wynikiem „UPDATE 0”, podczas gdy rzeczywista wygenerowana kolumna w wersji 12 nadal będzie zwraca BŁĄD, nawet jeśli nie znaleziono żadnego wiersza do aktualizacji.
- Podczas próby aktualizacji kolumny zysku, która „powinna” zawsze zwracać BŁĄD, jeśli określona wartość jest taka sama jak prawidłowo „wygenerowana” wartość, powiedzie się. Ostatecznie dane są jednak poprawne, jeśli chcemy zwrócić BŁĄD, jeśli kolumna jest określona.
Dokumentacja i społeczność PostgreSQL
Oficjalna dokumentacja kolumn generowanych przez PostgreSQL znajduje się na oficjalnej stronie PostgreSQL. Sprawdź, kiedy pojawią się nowe główne wersje PostgreSQL, aby odkryć nowe funkcje, gdy się pojawią.
Podczas gdy generowane kolumny w PostgreSQL 12 są dość proste, implementacja podobnej funkcjonalności w poprzednich wersjach może stać się znacznie bardziej skomplikowana. Społeczność PostgreSQL jest bardzo aktywną, ogromną, ogólnoświatową i wielojęzyczną społecznością, której celem jest pomaganie ludziom na każdym poziomie doświadczenia PostgreSQL w rozwiązywaniu problemów i tworzeniu nowych rozwiązań, takich jak ten.
- IRC :Freenode ma bardzo aktywny kanał o nazwie #postgres, w którym użytkownicy pomagają sobie nawzajem w zrozumieniu koncepcji, naprawianiu błędów lub znajdowaniu innych zasobów. Pełną listę dostępnych kanałów freenode dla wszystkich rzeczy związanych z PostgreSQL można znaleźć na stronie PostgreSQL.org.
- Listy mailingowe :PostgreSQL ma kilka list dyskusyjnych, do których można dołączyć. Pytania/problemy z dłuższym formularzem można przesyłać tutaj i mogą one dotrzeć do znacznie większej liczby osób niż IRC w danym momencie. Listy można znaleźć na stronie PostgreSQL, a listy pgsql-general lub pgsql-admin są dobrymi zasobami.
- Zwolnienie :Społeczność PostgreSQL również kwitnie na Slacku i można do niej dołączyć na postgresteam.slack.com. Podobnie jak IRC, aktywna społeczność jest dostępna, aby odpowiadać na pytania i angażować się we wszystkie sprawy związane z PostgreSQL.