PostgreSQL 12 идва с нова функция, наречена генерирани колони . Други популярни RDBMS вече поддържат генерирани колони като „изчислени колони“ или „виртуални колони“. С Postgres 12 вече можете да го използвате и в PostgreSQL. Прочетете, за да научите повече.
Какво е генерирана колона?
Генерираната колона е нещо като изглед, но за колони. Ето един основен пример:
db=# CREATE TABLE t (w real, h real, area real GENERATED ALWAYS AS (w*h) STORED);
CREATE TABLE
db=# INSERT INTO t (w, h) VALUES (10, 20);
INSERT 0 1
db=# SELECT * FROM t;
w | h | area
----+----+------
10 | 20 | 200
(1 row)
db=#
Създадохме таблицаt с две редовни колони, наречени w из , и генерирана колона, наречена област . Стойността на площа се изчислява по време на създаване на ред и се запазва на диска.
Стойността на генерираните колони се преизчислява, когато редът се актуализира:
db=# UPDATE t SET w=40;
UPDATE 1
db=# SELECT * FROM t;
w | h | area
----+----+------
40 | 20 | 800
(1 row)
db=#
По-рано такава функционалност обикновено се постигаше с тригери, но с генерирани колони това става много по-елегантно и по-чисто.
Няколко точки, които трябва да знаете за генерираните колони:
- Постоянство :В момента стойността на генерираните колони трябва да бъде запазена и не може да бъде изчислена в движение по време на заявка. Ключовата дума „STORED“ трябва да присъства в дефиницията на колоната.
- Изразът :Изразът, използван за изчисляване на стойността, трябва да енеизменяем , тоест трябва да е детерминистично. Може да зависи от други колони, но не и от други генерирани колони на таблицата.
- Индекси :Генерираните колони могат да се използват в индекси, но не могат да се използват като ключ на дял за разделени таблици.
- Копиране и pg_dump :Стойностите на генерираните колони се пропускат в изхода на командите “pg_dump” и “COPY table”, тъй като не е необходимо. Можете изрично да ги включите в COPY, като използвате
COPY (SELECT * FROM t) TO STDOUT
вместоCOPY t TO STDOUT
.
Практически пример
Нека добавим поддръжка за пълно текстово търсене към таблица, използвайки генерирани колони. Ето таблица, която съхранява целия текст на всички пиеси на Шекспир:
CREATE TABLE scenes (
workid text, -- denotes the name of the play (like "macbeth")
act integer, -- the act (like 1)
scene integer, -- the scene within the act (like 7)
description text, -- short desc of the scene (like "Macbeth's castle.")
body text -- full text of the scene
);
Ето как изглеждат данните:
shakespeare=# SELECT workid, act, scene, description, left(body, 200) AS body_start
shakespeare-# FROM scenes WHERE workid='macbeth' AND act=1 AND scene=1;
workid | act | scene | description | body_start
---------+-----+-------+-----------------+----------------------------------------------
macbeth | 1 | 1 | A desert place. | [Thunder and lightning. Enter three Witches]+
| | | | +
| | | | First Witch: When shall we three meet again +
| | | | In thunder, lightning, or in rain? +
| | | | +
| | | | Second Witch: When the hurlyburly's done, +
| | | | When the battle's lost and won. +
| | | |
(1 row)
Ще добавим колона, която ще съдържа лексемите в стойността на “body”. Функцията to_tsvector връща лексемите, от които се нуждаем:
shakespeare=# SELECT to_tsvector('english', 'move moving moved movable mover movability');
to_tsvector
-------------------------------------
'movabl':4,6 'move':1,2,3 'mover':5
(1 row)
Типът на стойността, върната от to_tsvector
е цветен.
Нека променим таблицата, за да добавим генерирана колона:
ALTER TABLE scenes
ADD tsv tsvector
GENERATED ALWAYS AS (to_tsvector('english', body)) STORED;
Можете да видите промяната с \d
:
shakespeare=# \d scenes
Table "public.scenes"
Column | Type | Collation | Nullable | Default
-------------+----------+-----------+----------+----------------------------------------------------------------------
workid | text | | not null |
act | integer | | not null |
scene | integer | | not null |
description | text | | |
body | text | | |
tsv | tsvector | | | generated always as (to_tsvector('english'::regconfig, body)) stored
Indexes:
"scenes_pkey" PRIMARY KEY, btree (workid, act, scene)
И точно така, вече можете да извършвате пълнотекстови търсения:
shakespeare=# SELECT
workid, act, scene, ts_headline(body, q)
FROM (
SELECT
workid, act, scene, body, ts_rank(tsv, q) as rank, q
FROM
scenes, plainto_tsquery('uneasy head') q
WHERE
tsv @@ q
ORDER BY
rank DESC
LIMIT
5
) p
ORDER BY
rank DESC;
workid | act | scene | ts_headline
----------+-----+-------+-----------------------------------------------------------
henry4p2 | 3 | 1 | <b>Uneasy</b> lies the <b>head</b> that wears a crown. +
| | | +
| | | Enter WARWICK and Surrey +
| | | +
| | | Earl of Warwick
henry5 | 2 | 2 | <b>head</b> assembled them? +
| | | +
| | | Lord Scroop: No doubt, my liege, if each man do his best.+
| | | +
| | | Henry V: I doubt not that; since we are well persuaded +
| | | We carry not a heart with us from hence
(2 rows)
shakespeare=#
Прочетете повече
Ако имате нужда от предварително изчислени / „кеширани“ данни, особено при натоварване от няколко записвания и много четения, генерираните колони трябва да помогнат за опростяване на кода на приложението/сървъра много.
Можете да прочетете документацията v12 на CREATE TABLE и ALTER TABLE, за да видите актуализирания синтаксис.