Creare e gestire le colonne generate

Una colonna generata è una colonna che viene sempre calcolata da altre colonne in una riga. Queste colonne possono semplificare una query, risparmiare il costo della valutazione di un'espressione al momento della query e possono essere indicizzate o utilizzate come chiave esterna. Questa pagina descrive come gestire questo tipo di colonna nel database per i database in dialetto GoogleSQL e PostgreSQL.

Aggiungere una colonna generata a una nuova tabella

Nel seguente snippet CREATE TABLE, creiamo una tabella per memorizzare informazioni sugli utenti. Abbiamo colonne per FirstName e LastName e definiamo una colonna generata per FullName, che è la concatenazione di FirstName e LastName. Il codice SQL tra parentesi è chiamato espressione di generazione.

Una colonna generata può essere contrassegnata come STORED per risparmiare il costo della valutazione dell'espressione al momento della query. Di conseguenza, il valore di FullName viene calcolato solo quando viene inserita una nuova riga o quando FirstName o LastName viene aggiornato per una riga esistente. Il valore calcolato viene archiviato insieme alle altre colonne della tabella.

GoogleSQL

CREATE TABLE Users (
Id STRING(20) NOT NULL,
FirstName STRING(50),
LastName STRING(50),
Age INT64 NOT NULL,
FullName STRING(100) AS (FirstName || ' ' || LastName) STORED
) PRIMARY KEY (Id);

PostgreSQL

CREATE TABLE users (
id VARCHAR(20) NOT NULL,
firstname VARCHAR(50),
lastname VARCHAR(50),
age BIGINT NOT NULL,
fullname VARCHAR(100) GENERATED ALWAYS AS (firstname || ' ' || lastname) STORED,
PRIMARY KEY(id)
);

Puoi creare una colonna generata non archiviata omettendo l'attributo STORED nel DDL. Questo tipo di colonna generata viene valutata al momento della query e può semplificare una query. In PostgreSQL, puoi creare una colonna generata non memorizzata utilizzando l'attributo VIRTUAL.

GoogleSQL

FullName STRING(MAX) AS (CONCAT(FirstName, " ", LastName))

PostgreSQL

fullname text GENERATED ALWAYS AS (firstname || ' ' || lastname) VIRTUAL
  • expression può essere qualsiasi espressione SQL valida assegnibile al tipo di dati della colonna con le seguenti limitazioni.

    • L'espressione può fare riferimento solo alle colonne della stessa tabella.

    • L'espressione non può contenere sottoquery.

    • Le espressioni con funzioni non deterministiche come PENDING_COMMIT_TIMESTAMP(), CURRENT_DATE() e CURRENT_TIMESTAMP() non possono essere trasformate in una colonna generata da STORED o in una colonna generata che è indicizzata.

    • Non puoi modificare l'espressione di una colonna STORED o generata indicizzata.

  • L'attributo STORED che segue l'espressione memorizza il risultato dell'espressione insieme alle altre colonne della tabella. Gli aggiornamenti successivi a una delle colonne a cui si fa riferimento fanno sì che Spanner rivaluti e memorizzi l'espressione.

  • Le colonne generate che non sono STORED non possono essere contrassegnate come NOT NULL.

  • Non sono consentite scritture dirette nelle colonne generate.

  • L'opzione di colonna allow_commit_timestamp non è consentita nelle colonne generate o nelle colonne a cui fanno riferimento le colonne generate.

  • Per le colonne STORED o generate indicizzate, non puoi modificare il tipo di dati della colonna o delle colonne a cui fa riferimento la colonna generata.

  • Non puoi eliminare una colonna a cui fa riferimento una colonna generata.

  • Puoi utilizzare una colonna generata come chiave primaria con le seguenti limitazioni aggiuntive:

    • La chiave primaria generata non può fare riferimento ad altre colonne generate.

    • La chiave primaria generata può fare riferimento a un massimo di una colonna non chiave.

    • La chiave primaria generata non può dipendere da una colonna non chiave con una clausola DEFAULT.

  • Quando utilizzi le colonne chiave generate, si applicano le seguenti regole:

    • API di lettura: devi specificare completamente le colonne chiave, incluse quelle generate.
    • API di mutazione: per INSERT, INSERT_OR_UPDATE e REPLACE, Spanner non consente di specificare le colonne chiave generate. Per UPDATE, puoi specificare facoltativamente le colonne chiave generate. PerDELETE, devi specificare completamente le colonne chiave, incluse le chiavi generate.
    • DML: non puoi scrivere esplicitamente nelle chiavi generate nelle istruzioni INSERT o UPDATE.
    • Query: in generale, ti consigliamo di utilizzare la colonna della chiave generata come filtro nella query. Se vuoi, se l'espressione per la colonna della chiave generata utilizza una sola colonna come riferimento, la query può applicare una condizione di uguaglianza (=) o IN alla colonna di riferimento. Per ulteriori informazioni e un esempio, consulta Creare una chiave univoca derivata da una colonna di valori.

È possibile eseguire query sulla colonna generata come su qualsiasi altra colonna, come mostrato nell'esempio seguente.

GoogleSQL

SELECT Id, FullName
FROM Users;

PostgreSQL

SELECT id, fullname
FROM users;

La query che utilizza Fullname è equivalente alla query con l'espressione generata. Di conseguenza, una colonna generata può semplificare la query.

GoogleSQL

SELECT Id, ARRAY_TO_STRING([FirstName, LastName], " ") as FullName
FROM Users;

PostgreSQL

SELECT id, firstname || ' ' || lastname as fullname
FROM users;

Creare un indice su una colonna generata

Puoi anche indicizzare o utilizzare una colonna generata come chiave esterna.

Per facilitare le ricerche nella colonna FullName generata, possiamo creare un indice secondario come mostrato nello snippet seguente.

GoogleSQL

CREATE INDEX UsersByFullName ON Users (FullName);

PostgreSQL

CREATE INDEX UserByFullName ON users (fullname);

Aggiungere una colonna generata a una tabella esistente

Utilizzando la seguente dichiarazione ALTER TABLE, possiamo aggiungere una colonna generata alla tabella Users per generare e memorizzare le iniziali dell'utente.

GoogleSQL

ALTER TABLE Users ADD COLUMN Initials STRING(2)
AS (ARRAY_TO_STRING([SUBSTR(FirstName, 0, 1), SUBSTR(LastName, 0, 1)], "")) STORED;

PostgreSQL

ALTER TABLE users ADD COLUMN initials VARCHAR(2)
GENERATED ALWAYS AS (SUBSTR(firstname, 0, 1) || SUBSTR(lastname, 0, 1)) STORED;

Se aggiungi una colonna generata archiviata a una tabella esistente, viene avviata un'operazione di lunga durata per eseguire il backfill dei valori della colonna. Durante il backfill, le colonne generate memorizzate non possono essere lette o sottoposte a query. Lo stato del backfill è riportato nella tabella INFORMATION_SCHEMA.

Creare un indice parziale utilizzando una colonna generata

E se volessimo eseguire query solo sugli utenti maggiori di 18 anni? Una scansione completa della tabella sarebbe inefficiente, pertanto utilizziamo un indice parziale.

  1. Utilizza la seguente istruzione per aggiungere un'altra colonna generata che restituisce l'età dell'utente se è superiore a 18 anni e restituisce NULL in caso contrario.

    GoogleSQL

    ALTER TABLE Users ADD COLUMN AgeAbove18 INT64
    AS (IF(Age > 18, Age, NULL));
    

    PostgreSQL

    ALTER TABLE Users ADD COLUMN AgeAbove18 BIGINT
    GENERATED ALWAYS AS (nullif( Age , least( 18, Age) )) VIRTUAL;
    
  2. Crea un indice su questa nuova colonna e disattiva l'indicizzazione dei valori NULL con la parola chiave NULL_FILTERED in GoogleSQL o il predicato IS NOT NULL in PostgreSQL. Questo indice parziale è più piccolo e più efficiente di un indice normale perché esclude tutti gli utenti di età inferiore a 18 anni.

    GoogleSQL

    CREATE NULL_FILTERED INDEX UsersAbove18ByAge
    ON Users (AgeAbove18);
    

    PostgreSQL

    CREATE INDEX UsersAbove18ByAge ON users (AgeAbove18)
    WHERE AgeAbove18 IS NOT NULL;
    
  3. Per recuperare Id e Age di tutti gli utenti di età superiore a 18 anni, esegui la seguente query.

    GoogleSQL

    SELECT Id, Age
    FROM Users@{FORCE_INDEX=UsersAbove18ByAge}
    WHERE AgeAbove18 IS NOT NULL;
    

    PostgreSQL

    SELECT Id, Age
    FROM users /*@ FORCE_INDEX = UsersAbove18ByAge */
    WHERE AgeAbove18 IS NOT NULL;
    
  4. Per filtrare in base a un'età diversa, ad esempio per recuperare tutti gli utenti di età superiore ai 21 anni, utilizza lo stesso indice e filtra la colonna generata come segue:

    GoogleSQL

    SELECT Id, Age
    FROM Users@{FORCE_INDEX=UsersAbove18ByAge}
    WHERE AgeAbove18 > 21;
    

    PostgreSQL

    SELECT Id, Age
    FROM users /*@ FORCE_INDEX = UsersAbove18ByAge */
    WHERE AgeAbove18 > 21;
    

    Una colonna generata indicizzata può farti risparmiare il costo della valutazione di un'espressione al momento della query ed evitare di memorizzare i valori due volte (nella tabella di base e nell'indice) rispetto a una colonna generata STORED.

Rimuovere una colonna generata

La seguente istruzione DDL elimina una colonna generata dalla tabella Users:

GoogleSQL

  ALTER TABLE Users DROP COLUMN Initials;

PostgreSQL

  ALTER TABLE users DROP COLUMN initials;

Modificare un'espressione della colonna generata

GoogleSQL

ALTER TABLE Users ALTER COLUMN FullName STRING(100)
AS (ARRAY_TO_STRING(ARRAY_TO_STRING([LastName, FirstName ], " ")));

PostgreSQL

ALTER TABLE users ADD COLUMN Initials VARCHAR(2)
GENERATED ALWAYS AS (lastname || ' ' || firstname) VIRTUAL;

Non è consentito aggiornare l'espressione di una colonna generata STORED o di una colonna generata non archiviata e indicizzata.

Creare una chiave primaria in una colonna generata

In Spanner, puoi utilizzare una colonna generata da STORED nella chiave primaria.

L'esempio seguente mostra un'istruzione DDL che crea la tabella UserInfoLog con una colonna ShardId generata. Il valore della colonna ShardId dipende da un'altra colonna. Viene derivato utilizzando una funzione MOD sulla colonna UserId. ShardId è dichiarato come parte della chiave primaria.

GoogleSQL

CREATE TABLE UserInfoLog (
  ShardId INT64 NOT NULL
  AS (MOD(UserId, 2048)) STORED,
  UserId INT64 NOT NULL,
  FullName STRING(1024) NOT NULL,
) PRIMARY KEY (ShardId, UserId);

PostgreSQL

CREATE TABLE UserInfoLog (
  ShardId BIGINT GENERATED ALWAYS
  AS (MOD(UserId, '2048'::BIGINT)) STORED NOT NULL,
  UserId BIGINT NOT NULL,
  FullName VARCHAR(1024) NOT NULL,
  PRIMARY KEY(ShardId, UserId));

In genere, per accedere in modo efficiente a una riga specifica, devi specificare tutte le colonne chiave. Nell'esempio precedente, ciò significa fornire sia ShardId che UserId. Tuttavia, a volte Spanner può dedurre il valore della colonna della chiave primaria generata se dipende da un'altra colonna e se il valore della colonna da cui dipende è completamente determinato. Questo accade se la colonna a cui fa riferimento la colonna della chiave primaria generata soddisfa una delle seguenti condizioni:

  • È uguale a un valore costante o a un parametro vincolato nella clausola WHERE oppure
  • Il valore viene impostato da un operatore IN nella clausola WHERE
  • Il valore viene ottenuto da una condizione di join equi

Ad esempio, per la seguente query:

GoogleSQL

SELECT * FROM UserInfoLog
AS T WHERE T.UserId=1;

PostgreSQL

SELECT * FROM UserInfoLog
AS T WHERE T.UserId=1;

Spanner può dedurre il valore di ShardId dal UserId fornito. La query precedente è equivalente alla seguente query dopo l'ottimizzazione della query:

GoogleSQL

SELECT * FROM UserInfoLog
AS T WHERE T.ShardId = MOD(1, 2048)
AND T.UserId=1;

PostgreSQL

SELECT * FROM UserInfoLog
AS T WHERE T.ShardId = MOD(1, 2048)
AND T.UserId=1;

L'esempio seguente mostra come creare la tabella Students e utilizzare un'espressione che recupera il campo id della colonna JSON StudentInfo e lo utilizza come chiave principale:

GoogleSQL

CREATE TABLE Students (
  StudentId INT64 NOT NULL
  AS (CAST(JSON_VALUE(StudentInfo, "$.id") AS INT64)) STORED,
  StudentInfo JSON NOT NULL,
) PRIMARY KEY (StudentId);

PostgreSQL

CREATE TABLE Students (
  StudentId BIGINT GENERATED ALWAYS
  AS (((StudentInfo ->> 'id'::TEXT))::BIGINT) STORED NOT NULL,
  StudentInfo JSONB NOT NULL,
  PRIMARY KEY(StudentId));

Visualizzare le proprietà di una colonna generata

INFORMATION_SCHEMA di Spanner contiene informazioni sulle colonne generate nel database. Di seguito sono riportati alcuni esempi di domande a cui puoi rispondere quando esegui una query sullo schema delle informazioni.

Quali colonne generate sono definite nel mio database?

GoogleSQL

SELECT c.TABLE_NAME, c.COLUMN_NAME, C.IS_STORED
FROM INFORMATION_SCHEMA.COLUMNS as c
WHERE c.GENERATION_EXPRESSION IS NOT NULL;

PostgreSQL

SELECT c.TABLE_NAME, c.COLUMN_NAME, C.IS_STORED
FROM INFORMATION_SCHEMA.COLUMNS as c
WHERE c.GENERATION_EXPRESSION IS NOT NULL;

IS_STORED è YES per le colonne generate archiviate, NO per le colonne generate non archiviate o NULL per le colonne non generate.

Qual è lo stato attuale delle colonne generate nella tabella Users?

Se hai aggiunto una colonna generata a una tabella esistente, ti consigliamo di passare SPANNER_STATE in una query per scoprire lo stato corrente della colonna. SPANNER_STATE restituisce i seguenti valori:

  • COMMITTED: la colonna è completamente utilizzabile.
  • WRITE_ONLY: la colonna è in fase di backfill. Non è consentita la lettura.

Utilizza la seguente query per trovare lo stato di una colonna:

GoogleSQL

SELECT c.TABLE_NAME, c.COLUMN_NAME, c.SPANNER_STATE
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_NAME="Users" AND c.GENERATION_EXPRESSION IS NOT NULL;

PostgreSQL

SELECT c.TABLE_NAME, c.COLUMN_NAME, c.SPANNER_STATE
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_NAME='users' AND c.GENERATION_EXPRESSION IS NOT NULL;

Nota: a una colonna generata non memorizzata è possibile accedere solo utilizzando la query SQL. Tuttavia, se è indicizzato, puoi utilizzare l'API di lettura per accedere al valore dall'indice.

Prestazioni

Una colonna generata da STORED non influisce sul rendimento di un'operazione di lettura o query. Tuttavia, le colonne generate non archiviate utilizzate in una query possono influire sulle sue prestazioni a causa dell'overhead della valutazione dell'espressione della colonna generata.

Il rendimento delle operazioni di scrittura (istruzioni DML e mutazioni) è interessato quando si utilizza una colonna STORED generata o una colonna generata indicizzata. Il sovraccarico è dovuto alla valutazione dell'espressione della colonna generata quando l'operazione di scrittura inserisce o modifica una delle colonne a cui fa riferimento l'espressione della colonna generata. Poiché l'overhead varia a seconda del carico di lavoro di scrittura per l'applicazione, della progettazione dello schema e delle caratteristiche del set di dati, ti consigliamo di eseguire il benchmark delle applicazioni prima di utilizzare una colonna generata.

Passaggi successivi