PostgreSQL 12 est livré avec une nouvelle fonctionnalité appelée colonnes générées . D'autres SGBDR populaires prennent déjà en charge les colonnes générées en tant que « colonnes calculées » ou « colonnes virtuelles ». Avec Postgres 12, vous pouvez désormais l'utiliser également dans PostgreSQL. Lisez la suite pour en savoir plus.
Qu'est-ce qu'une colonne générée ?
Une colonne générée est un peu comme une vue, mais pour les colonnes. Voici un exemple de base :
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=#
Nous avons créé un tableau t avec deux colonnes régulières appelées w et h , et une colonne générée appelée area . La valeur de surface est calculé au moment de la création de la ligne et est conservé sur le disque.
La valeur des colonnes générées est recalculée lors de la mise à jour de la ligne :
db=# UPDATE t SET w=40;
UPDATE 1
db=# SELECT * FROM t;
w | h | area
----+----+------
40 | 20 | 800
(1 row)
db=#
Une telle fonctionnalité était auparavant généralement obtenue avec des déclencheurs, mais avec les colonnes générées, cela devient beaucoup plus élégant et plus propre.
Quelques points à connaître sur les colonnes générées :
- Persistance :Actuellement, la valeur des colonnes générées doit être persistante et ne peut pas être calculée à la volée au moment de la requête. Le mot clé "STORED" doit être présent dans la définition de la colonne.
- L'expression :L'expression utilisée pour calculer la valeur doit être immuable , c'est-à-dire qu'il doit être déterministe. Cela peut dépendre d'autres colonnes, mais pas d'autres colonnes générées, de la table.
- Index :Les colonnes générées peuvent être utilisées dans les index, mais ne peuvent pas être utilisées comme clé de partition pour les tables partitionnées.
- Copier et pg_dump :Les valeurs des colonnes générées sont omises dans la sortie des commandes « pg_dump » et « COPY table », car elles sont inutiles. Vous pouvez les inclure explicitement dans COPY en utilisant
COPY (SELECT * FROM t) TO STDOUT
plutôt queCOPY t TO STDOUT
.
Un exemple pratique
Ajoutons la prise en charge de la recherche en texte intégral à une table à l'aide de colonnes générées. Voici un tableau qui stocke l'intégralité du texte de toutes les pièces de Shakespeare :
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
);
Voici à quoi ressemblent les données :
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)
Nous allons ajouter une colonne qui contiendra les lexèmes dans la valeur de "body". La fonction to_tsvector renvoie les lexèmes dont nous avons besoin :
shakespeare=# SELECT to_tsvector('english', 'move moving moved movable mover movability');
to_tsvector
-------------------------------------
'movabl':4,6 'move':1,2,3 'mover':5
(1 row)
Le type de la valeur renvoyée par to_tsvector
est tsvecteur.
Modifions le tableau pour ajouter une colonne générée :
ALTER TABLE scenes
ADD tsv tsvector
GENERATED ALWAYS AS (to_tsvector('english', body)) STORED;
Vous pouvez voir le changement avec \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)
Et juste comme ça, vous pouvez maintenant faire des recherches plein texte :
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=#
En savoir plus
Si vous avez besoin de données pré-calculées / "cachées", en particulier avec une charge de travail de peu d'écritures et de nombreuses lectures, les colonnes générées devraient vous aider à simplifier considérablement le code de votre application / côté serveur.
Vous pouvez lire la documentation v12 de CREATE TABLE et ALTER TABLE pour voir la syntaxe mise à jour.