PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Uma visão geral do pseudotipo de dados serial para PostgreSQL

Introdução


O PostgreSQL fornece nativamente uma rica diversidade de tipos de dados que suportam muitos casos de uso práticos. Este artigo apresenta a implementação especial de tipos de dados seriais normalmente usados ​​para criação de chaves primárias sintéticas.

Chaves exclusivas


Um preceito fundamental da teoria de design de banco de dados é que cada tupla (ou seja, linha) de uma relação (ou seja, tabela) deve ser identificada exclusivamente de outras tuplas. Os atributos, ou colunas, que juntos identificam distintamente uma tupla de todas as outras são chamados de "chave". Alguns puristas sustentam que qualquer objeto ou conceito modelado possui inerentemente um atributo ou conjunto de atributos que podem servir como uma chave e que é importante identificar esse conjunto de atributos-chave e utilizá-los para a seleção exclusiva de tuplas.

Mas, na prática, identificar um conjunto suficientemente grande de atributos que assegurem exclusividade para um objeto modelado pode ser impraticável e, portanto, para implementações do mundo real, os desenvolvedores geralmente recorrem a chaves sintéticas como substitutos. Ou seja, em vez de depender de alguma combinação de atributos reais, um valor interno ao banco de dados, normalmente valores inteiros incrementados e sem significado físico, é definido como uma chave. Além da simplicidade de uma única chave de coluna, o fato de não haver dependência no mundo real significa que fatores externos nunca podem forçar a necessidade de alterar o valor, como, por exemplo, pode ser o caso se o nome de uma pessoa for usado como chave... e aí a pessoa se casou ou entrou em um programa de proteção a testemunhas do governo federal e mudou de nome. Mesmo alguns valores comumente pensados ​​por leigos como únicos e imutáveis, como o número do seguro social dos EUA, também não o são:uma pessoa pode obter um novo SSN, e às vezes os SSNs são reutilizados.

Declarando um tipo de dados seriais


O PostgreSQL fornece uma declaração especial de tipo de dados para satisfazer essa necessidade de chaves sintéticas. Declarar uma coluna de tabela de banco de dados como tipo SERIAL satisfaz o requisito de chaves sintéticas fornecendo inteiros exclusivos nas inserções de novas tuplas. Esse pseudotipo de dados implementa uma coluna de tipo de dados inteiro com um valor padrão associado derivado por meio de uma chamada de função que fornece valores inteiros incrementados. Executando o seguinte código para criar uma tabela simples com uma coluna id do 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);

Ou seja, a palavra-chave "serial" como uma especificação de tipo de dados implica a execução de instruções DDL criando uma coluna do tipo inteiro com uma restrição NOT NULL, uma SEQUENCE e, em seguida, o padrão da coluna é ALTERED para chamar uma função interna acessando essa SEQUENCE.

A função interna nextval executa um serviço de incremento automático:cada vez que nextval é chamado, ele incrementa o contador de sequência especificado e retorna o valor recém-incrementado.

Você pode ver o resultado desse efeito examinando a definição da tabela:
postgres=# \d person
                   Table "public.person"
  Column   |  Type   |         Modifiers
-----------+---------+-----------------------------------------
 Id        | integer | not null default nextval('person_id_seq'::regclass)
 full_name | text    |

Inserindo valores seriais


Para fazer uso da funcionalidade de incremento automático, simplesmente inserimos linhas, contando com o valor padrão para a coluna serial:
INSERT INTO person (full_name) VALUES ('Alice');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
(1 row)

Vemos que um valor para a coluna id correspondente à nova linha “Alice” foi gerado automaticamente. Alternativamente, pode-se usar a palavra-chave DEFAULT se for desejado listar explicitamente todos os nomes das colunas:
INSERT INTO person (id, full_name) VALUES (DEFAULT, 'Bob');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
(2 rows)

onde vemos a funcionalidade de auto-incremento mais aparentemente, atribuindo o valor serialmente seguinte à nova linha para a segunda inserção de “Bob”.

Inserir várias linhas até funciona:
INSERT INTO person (full_name) VALUES ('Cathy'), ('David');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
(4 rows)
Baixe o whitepaper hoje PostgreSQL Management &Automation with ClusterControlSaiba o que você precisa saber para implantar, monitorar, gerenciar e dimensionar o PostgreSQLBaixe o whitepaper

Valores seriais ausentes


A função nextval integrada é otimizada para aplicativos sem bloqueio e de alta simultaneidade e, portanto, não respeita a reversão. Consequentemente, isso significa que pode haver valores ausentes na sequência. Abaixo, revertemos uma inserção, mas vemos que uma inserção subsequente obtém um novo valor que pula o valor que teria sido associado à transação abortada:
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)

A vantagem de não respeitar rollbacks é que outras sessões que tentam inserções simultâneas não são bloqueadas por outras sessões de inserção.

Outra maneira de acabar com valores ausentes é se as linhas forem excluídas:
DELETE FROM person WHERE full_name = 'Cathy';
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  6 | Fred
(4 rows)

Observe que mesmo após excluir a linha inserida mais recentemente correspondente ao maior valor da coluna id de incremento automático, o contador de sequência não é revertido, ou seja, mesmo após excluir a linha correspondente a 'Fred', para inserções subsequentes, o contador de sequência ainda retém o maior valor conhecido anteriormente e incrementa a partir daí:
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)

Lacunas ou valores ausentes como mostrados acima são vistos como um problema por alguns desenvolvedores de aplicativos porque na lista de discussão PostgreSQL General, há uma reiteração lenta, mas constante, da questão de como evitar lacunas de sequência ao empregar o pseudo-tipo de dados serial. Às vezes, não há nenhum requisito de negócios subjacente real, é apenas uma questão de aversão pessoal a valores ausentes. Mas há circunstâncias em que prevenir a falta de números é uma necessidade real, e isso é assunto para um próximo artigo.

NÃO VOCÊ NÃO PODE - SIM VOCÊ PODE!

A restrição NOT NULL imputada pelo pseudo-datatype serial protege contra a inserção de NULL para a coluna id ao rejeitar tais tentativas de inserção:
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).

Assim, temos a certeza de ter um valor para esse atributo.

No entanto, um problema que algumas pessoas encontram é que, como declarado acima, nada impede a inserção explícita de valores, ignorando o valor de autoincremento padrão derivado da invocação da função 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)

Mas duas inserções posteriores usando o padrão produzem um valor duplicado para a coluna id se não houver verificação de restrição dos valores da coluna em relação ao valor da sequência:
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 estivéssemos de fato usando a coluna serial id como chave, teríamos declarado como PRIMARY KEY ou pelo menos criado um UNIQUE INDEX. Se tivéssemos feito isso, a inserção 'Karen' acima teria falhado com um erro de chave duplicada. A versão mais recente do PostgreSQL inclui uma nova sintaxe de declaração de restrição 'gerada por padrão como identidade' que evita essa armadilha e alguns outros problemas herdados relacionados ao pseudo-tipo de dados serial.

Funções de manipulação de sequência


Além da função nextval que já mencionamos que avança a sequência e retorna o novo valor, existem algumas outras funções para consultar e definir os valores da sequência:a função currval retorna o valor obtido mais recentemente com nextval para a sequência especificada, a função lastval retorna o valor obtido mais recentemente com nextval para qualquer sequência, e a função setval define o valor atual de uma sequência. Essas funções são chamadas com consultas simples., por exemplo
SELECT currval('person_id_seq');
 currval
---------
       9
(1 row)

E observe que se uma chamada for feita para a função nextval independentemente de realmente executar uma inserção, ela incrementará a sequência e isso será refletido nas inserções subsequentes:
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)

Conclusão


Introduzimos uma compreensão básica do pseudotipo de dados SERIAL do PostgreSQL para chaves sintéticas incrementadas automaticamente. Para ilustração neste artigo, usamos a declaração do tipo SERIAL, que cria uma coluna inteira de 4 bytes. O PostgreSQL acomoda diferentes necessidades de intervalo com os pseudotipos de dados SMALLSERIAL e BIGSERIAL para, respectivamente, tamanhos de coluna de 2 e 8 bytes. Procure um artigo futuro sobre um meio de abordar a necessidade de sequências sem valores ausentes.