PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Una panoramica dello pseudo-tipo di dati seriale per PostgreSQL

Introduzione

PostgreSQL fornisce in modo nativo una ricca varietà di tipi di dati che supportano molti casi d'uso pratici. Questo articolo introduce l'implementazione speciale dei tipi di dati seriali generalmente utilizzati per la creazione di chiavi primarie sintetiche.

Chiavi univoche

Un precetto fondamentale della teoria della progettazione di database è che ogni tupla (cioè riga) di una relazione (cioè tabella) deve essere identificata in modo univoco da altre tuple. Gli attributi, o colonne, che insieme identificano distintamente una tupla da tutte le altre sono detti "chiave". Alcuni puristi sostengono che qualsiasi oggetto o concetto modellato possiede intrinsecamente un attributo o un insieme di attributi che possono fungere da chiave e che è importante identificare questo insieme di attributi chiave e utilizzarli per la selezione univoca delle tuple.

Ma in pratica, identificare un insieme sufficientemente ampio di attributi che assicuri l'unicità per un oggetto modellato può essere poco pratico, e quindi per le implementazioni nel mondo reale, gli sviluppatori spesso si rivolgono a chiavi sintetiche come surrogato. Cioè, piuttosto che fare affidamento su una combinazione di attributi effettivi, un valore interno al database, valori interi in genere incrementati e altrimenti privo di significato fisico è definito come chiave. Oltre alla semplicità di una singola chiave di colonna, il fatto che non vi sia alcuna dipendenza dal mondo reale significa che fattori esterni non possono mai imporre la necessità di modificare il valore, come ad esempio, potrebbe essere il caso se il nome di una persona fosse utilizzato come chiave ... e poi la persona si è sposata o è entrata in un programma di protezione dei testimoni del governo federale e ha cambiato il proprio nome. Anche alcuni valori comunemente ritenuti unici e immutabili dai profani, come il numero di previdenza sociale statunitense, non lo sono:una persona può ottenere un nuovo SSN e talvolta i SSN vengono riutilizzati.

Dichiarazione di un tipo di dati seriale

PostgreSQL fornisce una speciale dichiarazione del tipo di dati per soddisfare questa esigenza di chiavi sintetiche. La dichiarazione di una colonna di una tabella di database come tipo SERIAL soddisfa il requisito per le chiavi sintetiche fornendo numeri interi univoci su inserimenti di nuove tuple. Questo pseudo-tipo di dati implementa una colonna di tipo di dati intero con un valore predefinito associato derivato tramite una chiamata di funzione che fornisce valori interi incrementati. Eseguendo il codice seguente per creare una tabella semplice con una colonna id di tipo serial:

CREATE TABLE person (id serial, full_name text);
actually executes the following DDL
CREATE TABLE person (
    id integer NOT NULL,
    full_name text
);

CREATE SEQUENCE person_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
ALTER SEQUENCE person_id_seq OWNED BY person.id;
ALTER TABLE ONLY person
    ALTER COLUMN id
    SET DEFAULT nextval('person_id_seq'::regclass);

Cioè, la parola chiave "serial" come specifica del tipo di dati implica l'esecuzione di istruzioni DDL che creano una colonna di tipo intero con un vincolo NOT NULL, una SEQUENZA e quindi l'impostazione predefinita della colonna è ALTERED per chiamare una funzione incorporata che accede a tale SEQUENZA.

La funzione incorporata nextval esegue un servizio di autoincremento:ogni volta che viene chiamato nextval, incrementa il contatore di sequenza specificato e restituisce il valore appena incrementato.

Puoi vedere il risultato di questo effetto esaminando la definizione della tabella:

postgres=# \d person
                   Table "public.person"
  Column   |  Type   |         Modifiers
-----------+---------+-----------------------------------------
 Id        | integer | not null default nextval('person_id_seq'::regclass)
 full_name | text    |

Inserimento di valori seriali

Per utilizzare la funzionalità di incremento automatico, inseriamo semplicemente delle righe, basandoci sul valore predefinito per la colonna seriale:

INSERT INTO person (full_name) VALUES ('Alice');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
(1 row)

Vediamo che è stato generato automaticamente un valore per la colonna id corrispondente alla nuova riga "Alice". In alternativa, è possibile utilizzare la parola chiave DEFAULT se si desidera elencare esplicitamente tutti i nomi delle colonne:

INSERT INTO person (id, full_name) VALUES (DEFAULT, 'Bob');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
(2 rows)

dove vediamo più apparentemente la funzionalità di autoincremento, assegnando il valore serially-next alla nuova riga per il secondo inserto di “Bob”.

L'inserimento di più righe funziona anche:

INSERT INTO person (full_name) VALUES ('Cathy'), ('David');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
(4 rows)
Scarica il whitepaper oggi Gestione e automazione di PostgreSQL con ClusterControlScopri cosa devi sapere per distribuire, monitorare, gestire e ridimensionare PostgreSQLScarica il whitepaper

Valori seriali mancanti

La funzione integrata nextval è ottimizzata per applicazioni non bloccanti e ad alta concorrenza e quindi non rispetta il rollback. Di conseguenza, ciò significa che potrebbero esserci valori mancanti nella sequenza. Di seguito, eseguiamo il rollback di un inserto, ma poi vediamo che un inserimento successivo ottiene un nuovo valore che salta il valore che sarebbe stato associato alla transazione interrotta:

BEGIN TRANSACTION;
INSERT INTO person (full_name) VALUES ('Eve');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
  5 | Eve
(5 rows)
ROLLBACK;
INSERT INTO person (full_name) VALUES ('Fred');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
  6 | Fred
(5 rows)

Il vantaggio di non rispettare i rollback è che altre sessioni che tentano inserimenti simultanei non vengono bloccate da altre sessioni di inserimento.

Un altro modo per ritrovarsi con valori mancanti è eliminare le righe:

DELETE FROM person WHERE full_name = 'Cathy';
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  6 | Fred
(4 rows)

Si noti che anche dopo aver eliminato la riga inserita più di recente corrispondente al valore della colonna ID di incremento automatico più grande, il contatore di sequenza non viene ripristinato, ovvero, anche dopo aver eliminato la riga corrispondente a "Fred", per gli inserimenti successivi il contatore di sequenza conserva ancora il valore più grande precedentemente noto e aumenta da lì:

DELETE FROM person WHERE full_name = 'Fred';
INSERT INTO person (full_name) VALUES ('Gina');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
(4 rows)

Secondo quanto riferito, le lacune o i valori mancanti come mostrato sopra sono visti come un problema da alcuni sviluppatori di applicazioni perché nella mailing list generale di PostgreSQL c'è una reiterazione lenta ma costante della domanda su come evitare lacune di sequenza quando si utilizza lo pseudo-tipo di dati seriale. A volte non esiste un reale requisito aziendale sottostante, è solo una questione di avversione personale per i valori mancanti. Ma ci sono circostanze in cui prevenire i numeri mancanti è una reale necessità, e questo è l'argomento di un articolo successivo.

NO NON PUOI - SÌ PUOI!

Il vincolo NOT NULL imputato dallo pseudo-tipo di dati seriale protegge dall'inserimento di NULL per la colonna id rifiutando tali tentativi di inserimento:

INSERT INTO person (id, full_name) VALUES (NULL, 'Henry');
ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, Henry).

Pertanto, abbiamo la certezza di avere un valore per quell'attributo.

Tuttavia, un problema che alcune persone incontrano è che, come dichiarato sopra, nulla impedisce l'inserimento esplicito di valori, bypassando il valore di autoincremento predefinito derivato tramite l'invocazione della funzione nextval:

INSERT INTO person (id, full_name) VALUES (9, 'Ingrid');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
(5 rows)

Ma poi due inserimenti successivi usando il valore predefinito producono un valore duplicato per la colonna id se non c'è un controllo dei vincoli dei valori della colonna rispetto al valore della sequenza:

INSERT INTO person (full_name) VALUES ('James');
INSERT INTO person (full_name) VALUES ('Karen');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
  8 | James
  9 | Karen
(7 rows)

Se avessimo effettivamente utilizzato la colonna serial id come chiave, l'avremmo dichiarata come CHIAVE PRIMARIA o almeno creato un INDICE UNICO. Se lo avessimo fatto, l'inserto "Karen" sopra avrebbe fallito con un errore di chiave duplicata. La versione più recente di PostgreSQL include una nuova sintassi di dichiarazione di vincolo "generata per impostazione predefinita come identità" che evita questa trappola e alcuni altri problemi legacy relativi allo pseudo-tipo di dati seriale.

Funzioni di manipolazione della sequenza

Oltre alla funzione nextval già menzionata che fa avanzare la sequenza e restituisce il nuovo valore, ci sono alcune altre funzioni per interrogare e impostare i valori della sequenza:la funzione currval restituisce il valore ottenuto più di recente con nextval per la sequenza specificata, la funzione lastval restituisce il valore ottenuto più di recente con nextval per qualsiasi sequenza e la funzione setval imposta il valore corrente di una sequenza. Queste funzioni vengono richiamate con semplici query., ad esempio

SELECT currval('person_id_seq');
 currval
---------
       9
(1 row)

E nota che se viene effettuata una chiamata alla funzione nextval indipendentemente dall'esecuzione effettiva di un inserimento, viene incrementata la sequenza e ciò si rifletterà negli inserimenti successivi:

SELECT nextval('person_id_seq');
 nextval
---------
      10
(1 row)
INSERT INTO person (full_name) VALUES ('Larry');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
  8 | James
  9 | Karen
 11 | Larry
(8 rows)

Conclusione

Abbiamo introdotto una comprensione di base dello pseudo-tipo di dati SERIAL di PostgreSQL per le chiavi sintetiche auto-incrementate. Per l'illustrazione in questo articolo, è stata utilizzata la dichiarazione di tipo SERIAL, che crea una colonna intera a 4 byte. PostgreSQL soddisfa diverse esigenze di intervallo con gli pseudo-tipi di dati SMALLSERIAL e BIGSERIAL rispettivamente per dimensioni di colonna di 2 e 8 byte. Cerca un articolo futuro su un mezzo per affrontare la necessità di sequenze senza valori mancanti.