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

Uma visão geral das colunas geradas para PostgreSQL

O PostgreSQL 12 vem com um ótimo novo recurso, Colunas Geradas. A funcionalidade não é exatamente uma novidade, mas a padronização, facilidade de uso, acessibilidade e desempenho foram aprimorados nesta nova versão.

Uma Coluna Gerada é uma coluna especial em uma tabela que contém dados gerados automaticamente de outros dados dentro da linha. O conteúdo da coluna gerada é preenchido e atualizado automaticamente sempre que os dados de origem, como quaisquer outras colunas na linha, são alterados.

Colunas geradas no PostgreSQL 12+

Nas versões recentes do PostgreSQL, as colunas geradas são um recurso interno que permite que as instruções CREATE TABLE ou ALTER TABLE adicionem uma coluna na qual o conteúdo é automaticamente 'gerado' como resultado de uma expressão. Essas expressões podem ser operações matemáticas simples de outras colunas ou uma função imutável mais avançada. Alguns benefícios da implementação de uma coluna gerada em um design de banco de dados incluem:

  • A capacidade de adicionar uma coluna a uma tabela contendo dados calculados sem a necessidade de atualizar o código do aplicativo para gerar os dados e incluí-los nas operações INSERT e UPDATE.
  • Reduzindo o tempo de processamento em instruções SELECT extremamente frequentes que processariam os dados em tempo real. Como o processamento dos dados é feito no momento de INSERT ou UPDATE, os dados são gerados uma vez e as instruções SELECT precisam apenas recuperar os dados. Em ambientes de leitura pesada, isso pode ser preferível, desde que o armazenamento de dados extra usado seja aceitável.
  • Como as colunas geradas são atualizadas automaticamente quando os próprios dados de origem são atualizados, adicionar uma coluna gerada adicionará uma garantia assumida de que os dados na coluna gerada estão sempre corretos.

No PostgreSQL 12, apenas o tipo ‘STORED’ de coluna gerada está disponível. Em outros sistemas de banco de dados, está disponível uma coluna gerada com um tipo ‘VIRTUAL’, que funciona mais como uma visualização onde o resultado é calculado em tempo real quando os dados são recuperados. Como a funcionalidade é muito semelhante às visualizações e simplesmente escreve a operação em uma instrução select, a funcionalidade não é tão benéfica quanto a funcionalidade 'ARMAZENADO' discutida aqui, mas há uma chance de que versões futuras incluam o recurso.

A criação de uma tabela com uma coluna gerada é feita ao definir a própria coluna. Neste exemplo, a coluna gerada é 'lucro' e é gerada automaticamente subtraindo o preço_compra das colunas preço_venda e, em seguida, multiplicado pela coluna quantidade_vendida.

CREATE TABLE public.transactions (

    transactions_sid serial primary key,

    transaction_date timestamp with time zone DEFAULT now() NOT NULL,

    product_name character varying NOT NULL,

    purchase_price double precision NOT NULL,

    sale_price double precision NOT NULL,

    quantity_sold integer NOT NULL,

    profit double precision NOT NULL GENERATED ALWAYS AS  ((sale_price - purchase_price) * quantity_sold) STORED

);

Neste exemplo, uma tabela de ‘transações’ é criada para rastrear algumas transações básicas e lucros de uma cafeteria imaginária. A inserção de dados nesta tabela mostrará alguns resultados imediatos.

​severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('House Blend Coffee', 5, 11.99, 1);

severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('French Roast Coffee', 6, 12.99, 4);

severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('BULK: House Blend Coffee, 10LB', 40, 100, 6);



severalnines=# SELECT * FROM public.transactions;

 transactions_sid |       transaction_date |          product_name | purchase_price | sale_price | quantity_sold | profit

------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------

                1 | 2020-02-28 04:50:06.626371+00 | House Blend Coffee             | 5 | 11.99 | 1 | 6.99

                2 | 2020-02-28 04:50:53.313572+00 | French Roast Coffee            | 6 | 12.99 | 4 | 27.96

                3 | 2020-02-28 04:51:08.531875+00 | BULK: House Blend Coffee, 10LB |             40 | 100 | 6 | 360

Ao atualizar a linha, a coluna gerada será atualizada automaticamente:
​severalnines=# UPDATE public.transactions SET sale_price = 95 WHERE transactions_sid = 3;

UPDATE 1



severalnines=# SELECT * FROM public.transactions WHERE transactions_sid = 3;

 transactions_sid |       transaction_date |          product_name | purchase_price | sale_price | quantity_sold | profit

------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------

                3 | 2020-02-28 05:55:11.233077+00 | BULK: House Blend Coffee, 10LB |             40 | 95 | 6 | 330

Isso garantirá que a coluna gerada esteja sempre correta, sem necessidade de lógica adicional no lado do aplicativo.

OBSERVAÇÃO:as colunas geradas não podem ser INSERTED ou UPDATED diretamente, e qualquer tentativa de fazê-lo retornará em um ERROR:

severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold, profit) VALUES ('BULK: House Blend Coffee, 10LB', 40, 95, 1, 95);

ERROR:  cannot insert into column "profit"

DETAIL:  Column "profit" is a generated column.



severalnines=# UPDATE public.transactions SET profit = 330 WHERE transactions_sid = 3;

ERROR:  column "profit" can only be updated to DEFAULT

DETAIL:  Column "profit" is a generated column.

Colunas geradas no PostgreSQL 11 e anteriores

Mesmo que as colunas geradas embutidas sejam novas na versão 12 do PostgreSQL, a funcionalidade ainda pode ser alcançada em versões anteriores, só precisa de um pouco mais de configuração com stored procedures e triggers. No entanto, mesmo com a capacidade de implementá-lo em versões mais antigas, além da funcionalidade adicionada que pode ser benéfica, a conformidade estrita de entrada de dados é mais difícil de alcançar e depende dos recursos do PL/pgSQL e da engenhosidade de programação.

BÔNUS:O exemplo abaixo também funcionará no PostgreSQL 12+, portanto, se a funcionalidade adicionada com uma combinação de função/gatilho for necessária ou desejada em versões mais recentes, esta opção é um substituto válido e não restrito a apenas versões anteriores a 12.

Embora esta seja uma maneira de fazer isso em versões anteriores do PostgreSQL, há alguns benefícios adicionais desse método:

  • Como a imitação da coluna gerada usa uma função, cálculos mais complexos podem ser usados. Colunas geradas na versão 12 requerem operações IMUTÁVEIS, mas uma opção de gatilho/função pode usar um tipo de função STABLE ou VOLATILE com maiores possibilidades e provavelmente menor desempenho de acordo.
  • Usar uma função que tem a opção de ser STABLE ou VOLATILE também abre a possibilidade de UPDATE colunas adicionais, UPDATE outras tabelas, ou até mesmo criar novos dados via INSERTS em outras tabelas. (No entanto, embora essas opções de gatilho/função sejam muito mais flexíveis, isso não quer dizer que falte uma "Coluna Gerada" real, pois faz o que é anunciado com maior desempenho e eficiência.)

Neste exemplo, um gatilho/função é configurado para imitar a funcionalidade de uma coluna gerada pelo PostgreSQL 12+, junto com duas partes que geram uma exceção se um INSERT ou UPDATE tentar alterar a coluna gerada . Estes podem ser omitidos, mas se forem omitidos, exceções não serão levantadas, e os dados reais INSERTed ou UPDATEd serão descartados silenciosamente, o que geralmente não seria recomendado.

O próprio gatilho está configurado para executar BEFORE, o que significa que o processamento acontece antes da inserção real e requer o RETURN de NEW, que é o RECORD que é modificado para conter o novo valor da coluna gerada. Este exemplo específico foi escrito para rodar no PostgreSQL versão 11.

CREATE TABLE public.transactions (

    transactions_sid serial primary key,

    transaction_date timestamp with time zone DEFAULT now() NOT NULL,

    product_name character varying NOT NULL,

    purchase_price double precision NOT NULL,

    sale_price double precision NOT NULL,

    quantity_sold integer NOT NULL,

    profit double precision NOT NULL

);



CREATE OR REPLACE FUNCTION public.generated_column_function()

 RETURNS trigger

 LANGUAGE plpgsql

 IMMUTABLE

AS $function$

BEGIN



    -- This statement mimics the ERROR on built in generated columns to refuse INSERTS on the column and return an ERROR.

    IF (TG_OP = 'INSERT') THEN

        IF (NEW.profit IS NOT NULL) THEN

            RAISE EXCEPTION 'ERROR:  cannot insert into column "profit"' USING DETAIL = 'Column "profit" is a generated column.';

        END IF;

    END IF;



    -- This statement mimics the ERROR on built in generated columns to refuse UPDATES on the column and return an ERROR.

    IF (TG_OP = 'UPDATE') THEN

        -- Below, IS DISTINCT FROM is used because it treats nulls like an ordinary value. 

        IF (NEW.profit::VARCHAR IS DISTINCT FROM OLD.profit::VARCHAR) THEN

            RAISE EXCEPTION 'ERROR:  cannot update column "profit"' USING DETAIL = 'Column "profit" is a generated column.';

        END IF;

    END IF;



    NEW.profit := ((NEW.sale_price - NEW.purchase_price) * NEW.quantity_sold);

    RETURN NEW;



END;

$function$;




CREATE TRIGGER generated_column_trigger BEFORE INSERT OR UPDATE ON public.transactions FOR EACH ROW EXECUTE PROCEDURE public.generated_column_function();

OBSERVAÇÃO:Certifique-se de que a função tenha as permissões/propriedade corretas para ser executada pelo(s) usuário(s) do aplicativo desejado.

Como visto no exemplo anterior, os resultados são os mesmos nas versões anteriores com uma solução de função/gatilho:

​severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('House Blend Coffee', 5, 11.99, 1);

severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('French Roast Coffee', 6, 12.99, 4);

severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold) VALUES ('BULK: House Blend Coffee, 10LB', 40, 100, 6);



severalnines=# SELECT * FROM public.transactions;

 transactions_sid |       transaction_date |          product_name | purchase_price | sale_price | quantity_sold | profit

------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------

                1 | 2020-02-28 00:35:14.855511-07 | House Blend Coffee             | 5 | 11.99 | 1 | 6.99

                2 | 2020-02-28 00:35:21.764449-07 | French Roast Coffee            | 6 | 12.99 | 4 | 27.96

                3 | 2020-02-28 00:35:27.708761-07 | BULK: House Blend Coffee, 10LB |             40 | 100 | 6 | 360

A atualização dos dados será semelhante.

​severalnines=# UPDATE public.transactions SET sale_price = 95 WHERE transactions_sid = 3;

UPDATE 1



severalnines=# SELECT * FROM public.transactions WHERE transactions_sid = 3;

 transactions_sid |       transaction_date |          product_name | purchase_price | sale_price | quantity_sold | profit

------------------+-------------------------------+--------------------------------+----------------+------------+---------------+--------

                3 | 2020-02-28 00:48:52.464344-07 | BULK: House Blend Coffee, 10LB |             40 | 95 | 6 | 330

Por último, tentar INSERT ou ATUALIZAR a própria coluna especial resultará em um ERRO:

​severalnines=# INSERT INTO public.transactions (product_name, purchase_price, sale_price, quantity_sold, profit) VALUES ('BULK: House Blend Coffee, 10LB', 40, 95, 1, 95);

ERROR:  ERROR: cannot insert into column "profit"

DETAIL:  Column "profit" is a generated column.

CONTEXT:  PL/pgSQL function generated_column_function() line 7 at RAISE



severalnines=# UPDATE public.transactions SET profit = 3030 WHERE transactions_sid = 3;

ERROR:  ERROR: cannot update column "profit"

DETAIL:  Column "profit" is a generated column.

CONTEXT:  PL/pgSQL function generated_column_function() line 15 at RAISE

Neste exemplo, ele age de forma diferente da primeira configuração de coluna gerada de algumas maneiras que devem ser observadas:

  • Se a 'coluna gerada' tentar ser atualizada, mas nenhuma linha for encontrada para ser atualizada, ela retornará com sucesso com um resultado "UPDATE 0", enquanto uma coluna gerada real na versão 12 ainda será retorne um ERROR, mesmo que nenhuma linha seja encontrada para UPDATE.
  • Ao tentar atualizar a coluna de lucro, que 'deve' sempre retornar um ERRO, se o valor especificado for o mesmo que o valor 'gerado' corretamente, ele será bem-sucedido. Em última análise, os dados estão corretos, no entanto, se o desejo for retornar um ERRO se a coluna for especificada.

Documentação e comunidade PostgreSQL

A documentação oficial das Colunas Geradas do PostgreSQL está localizada no site oficial do PostgreSQL. Verifique novamente quando novas versões principais do PostgreSQL forem lançadas para descobrir novos recursos quando eles aparecerem.

Embora as colunas geradas no PostgreSQL 12 sejam bastante simples, implementar funcionalidades semelhantes em versões anteriores tem o potencial de se tornar muito mais complicado. A comunidade PostgreSQL é uma comunidade muito ativa, massiva, mundial e multilíngue, dedicada a ajudar pessoas de qualquer nível de experiência com PostgreSQL a resolver problemas e criar novas soluções como esta.

  • IRC :O Freenode tem um canal muito ativo chamado #postgres, onde os usuários se ajudam a entender conceitos, corrigir erros ou encontrar outros recursos. Uma lista completa de canais freenode disponíveis para todas as coisas do PostgreSQL pode ser encontrada no site PostgreSQL.org.
  • Listas de correspondência :PostgreSQL tem um punhado de listas de discussão que podem ser unidas. Perguntas/questões mais longas podem ser enviadas aqui e podem alcançar muito mais pessoas do que o IRC a qualquer momento. As listas podem ser encontradas no site do PostgreSQL, e as listas pgsql-general ou pgsql-admin são bons recursos.
  • Slack :A comunidade PostgreSQL também está prosperando no Slack e pode ser acessada em postgresteam.slack.com. Assim como o IRC, uma comunidade ativa está disponível para responder perguntas e se envolver em todas as coisas do PostgreSQL.