Become a member!

Tipos compostos no PostgreSQL: o recurso entre as colunas simples e o JSON

🌐
Este artigo também está disponível em outros idiomas:
🇪🇸 Español  •  🇩🇪 Deutsch  •  🇬🇧 English

Este artigo cobre tudo, do básico aos tópicos avançados, incluindo exemplos da documentação oficial. O post foi pensado para ser um material aprofundado que mostra como os tipos compostos podem ser usados para projetar schemas de banco de dados limpos, fáceis de manter e eficientes.


Sumário

  1. Introdução
  2. O que são os tipos compostos?
  3. Por que usar tipos compostos?
  4. Criando tipos compostos
  5. Usando tipos compostos em tabelas
  6. Inserindo dados em colunas de tipo composto
  7. Acessando e consultando os campos de tipos compostos
  8. Atualizando os campos de tipos compostos
  9. Tipos compostos em funções
  10. Indexando os campos de tipos compostos
  11. Considerações de desempenho
  12. Boas práticas no uso de tipos compostos
  13. Cenários de uso avançados
  14. Comparação com conceitos parecidos em outros bancos de dados
  15. Segurança e integridade dos dados
  16. Migração e evolução dos tipos compostos
  17. Armadilhas comuns e solução de problemas
  18. Desenvolvimentos futuros e propostas da comunidade
  19. Conclusão
  20. Referências

Uma gaivota voando sobre o mar em Fiumicino, ao lado de um farol verde do porto

1. Introdução

O PostgreSQL é famoso pela riqueza de recursos e pela extensibilidade. Entre os seus muitos recursos avançados, os tipos compostos se destacam como uma ferramenta versátil que permite aos desenvolvedores definir as próprias estruturas de dados. Este artigo pretende ser um guia de referência sobre os tipos compostos do PostgreSQL. Seja você DBA, desenvolvedor backend ou arquiteto, vai encontrar explicações detalhadas, exemplos práticos e dicas de desempenho para integrar os tipos compostos aos seus projetos.

Os tipos compostos ajudam a estruturar os dados de um jeito que espelha as entidades do mundo real. Em vez de espalhar dados relacionados por várias colunas ou tabelas, você pode encapsulá-los em uma unidade lógica e organizada. Neste guia vamos ver a teoria por trás dos tipos compostos, dar instruções passo a passo para criá-los e manipulá-los e discutir tópicos avançados como indexação, otimização de desempenho e evolução do schema.

Este post se inspira na documentação oficial do PostgreSQL sobre row types (PostgreSQL Row Types) e foi adaptado para trazer exemplos práticos e casos de uso reais.

2. O que são os tipos compostos?

Os tipos compostos no PostgreSQL são, basicamente, tipos de dados definidos pelo usuário que reúnem vários campos em uma única estrutura. Conceitualmente, são parecidos com os tipos record ou struct de muitas linguagens de programação. Um tipo composto define a estrutura de uma linha, e cada campo tem um nome e um tipo de dado associado.

2.1 Definição e sintaxe

A sintaxe básica para criar um tipo composto é a seguinte:

CREATE TYPE type_name AS (
    field1 data_type1,
    field2 data_type2,
    ...
);

Por exemplo, considere o seguinte tipo composto para um endereço:

CREATE TYPE address AS (
    street TEXT,
    city   TEXT,
    zip    CHAR(5)
);

Esta instrução define um tipo address com três campos: street, city e zip. Cada um desses campos tem um tipo de dado PostgreSQL correspondente. O design e as convenções de nomes devem refletir o agrupamento lógico dos seus dados.

2.2 Um pouco de história

Os tipos compostos fazem parte do PostgreSQL há muitos anos, sinal de como o projeto leva a sério um sistema de tipos avançado e a extensibilidade. Eles oferecem um nível de abstração que facilita modelar entidades complexas do mundo real sem precisar recorrer a várias tabelas normalizadas. A ideia é espelhar a forma como você estruturaria os dados no modelo de objetos da aplicação, mas diretamente no banco de dados.


3. Por que usar tipos compostos?

Os tipos compostos trazem várias vantagens no design de um schema de banco de dados. Estes são alguns dos principais motivos para usá-los:

3.1 Agrupamento lógico dos dados

Os tipos compostos permitem agrupar atributos relacionados. Um endereço (formado por rua, cidade e CEP), por exemplo, é naturalmente uma coisa só. Em vez de criar três colunas separadas, você pode definir uma única coluna do tipo address. Esse encapsulamento leva a um schema mais limpo e intuitivo.

3.2 Assinaturas de função mais simples

Quando você escreve stored procedures ou funções, pode precisar passar vários parâmetros relacionados. Definindo um tipo composto, você reduz o número de parâmetros que as funções exigem. As funções podem receber tipos compostos como argumentos ou retorná-los, o que simplifica o código e melhora a manutenção.

3.3 Integridade e consistência dos dados

Quando os dados estão encapsulados em um tipo composto, fica mais fácil garantir a consistência entre campos relacionados. As mudanças na estrutura do tipo composto se propagam automaticamente para todas as tabelas e funções que o usam, o que mantém o schema consistente conforme o modelo de dados evolui.

3.4 Mais legibilidade e manutenção

Os tipos compostos podem deixar o seu código SQL mais fácil de ler. Em vez de lidar com um monte de colunas relacionadas de alguma forma, você trabalha com um único campo composto. Isso reduz a bagunça e deixa mais evidentes as intenções por trás do schema.

3.5 Alinhamento com o design orientado a objetos

Para quem conhece programação orientada a objetos, os tipos compostos do PostgreSQL oferecem um conceito familiar: o record, ou objeto. Isso permite um mapeamento mais natural entre o modelo de domínio da aplicação e o schema do banco, reduzindo a distância mental entre código e dados.


4. Criando tipos compostos

Criar um tipo composto no PostgreSQL é simples. Como já vimos, você usa o comando CREATE TYPE seguido do nome e da lista de campos.

4.1 Exemplo básico

Vamos retomar o exemplo simples do endereço:

CREATE TYPE address AS (
    street TEXT,
    city   TEXT,
    zip    CHAR(5)
);

Este comando cria um tipo composto que depois pode ser usado em definições de tabelas, funções e muito mais.

4.2 Outros exemplos da documentação oficial

A documentação do PostgreSQL traz vários exemplos para ilustrar os tipos compostos. Um deles é a definição de um tipo para números complexos:

CREATE TYPE complex AS (
    r double precision,
    i double precision
);

Neste exemplo, o tipo complex representa um número complexo com uma parte real (r) e uma parte imaginária (i). É uma forma elegante de encapsular a aritmética dos números complexos dentro do banco.

4.3 Tipos compostos aninhados

Os tipos compostos também podem ser aninhados. Ou seja, um tipo composto pode ter outro tipo composto como um dos seus campos. Por exemplo:

CREATE TYPE full_address AS (
    street TEXT,
    city   TEXT,
    zip    CHAR(5),
    country TEXT
);

CREATE TYPE person AS (
    first_name TEXT,
    last_name  TEXT,
    home_address full_address
);

Aqui o tipo person inclui um campo home_address que é, ele mesmo, um tipo composto (full_address). Isso é especialmente útil para modelar estruturas de dados hierárquicas.

4.4 Alterando tipos compostos

Depois que um tipo composto é criado, nem sempre é simples mudar a sua estrutura. Ao contrário das tabelas, os tipos compostos não podem ser alterados diretamente com ALTER TYPE para adicionar ou remover atributos. Se você precisar mudar a estrutura, talvez tenha que apagar e recriar o tipo. Por exemplo:

DROP TYPE IF EXISTS address;
CREATE TYPE address AS (
    street TEXT,
    city   TEXT,
    zip    CHAR(5),
    state  CHAR(2)
);

Este exemplo mostra como apagar um tipo existente e recriá-lo com um campo a mais (state). Mas é preciso cuidado, porque apagar um tipo pode afetar as tabelas ou funções que dependem dele.


5. Usando tipos compostos em tabelas

Os tipos compostos são usados principalmente como tipo de dado de colunas em definições de tabelas. Eles permitem encapsular colunas relacionadas em uma única coluna composta.

5.1 Exemplo de definição de tabela

Vamos definir uma tabela chamada customers que usa o tipo composto address:

CREATE TABLE customers (
    id      SERIAL PRIMARY KEY,
    name    TEXT NOT NULL,
    contact address
);

Nesta tabela, a coluna contact é do tipo composto address, ou seja, vai guardar um registro com os valores de street, city e zip.

5.2 Vantagens das colunas compostas

  • Encapsulamento: as informações relacionadas ficam em um único campo, o que deixa o schema da tabela mais limpo.
  • Queries mais simples: ao ler ou atualizar informações relacionadas, você se refere a uma única coluna em vez de várias.
  • Agrupamento lógico: o modelo de dados reflete com mais fidelidade as entidades do mundo real, o que melhora a clareza e a manutenção.

5.3 Limitações

Os tipos compostos trazem muitas vantagens, mas também têm limitações. Por exemplo:

  • Alteração limitada: como já dito, mudar um tipo composto existente não é trivial.
  • Dificuldades na indexação: você não pode indexar diretamente uma coluna composta; em vez disso, precisa indexar campos específicos do tipo composto usando índices funcionais.

6. Inserindo dados em colunas de tipo composto

Inserir dados em colunas de tipo composto é simples. O PostgreSQL oferece várias formas de fazer isso: usando o construtor ROW() ou informando o valor composto diretamente.

6.1 Inserindo dados com o construtor ROW()

O construtor ROW() cria explicitamente um valor composto. Considere o exemplo a seguir:

INSERT INTO customers (name, contact)
VALUES ('John Doe', ROW('123 Maple St', 'Springfield', '12345'));

Aqui o construtor ROW() monta os valores individuais em um tipo composto que corresponde à estrutura do tipo address.

6.2 Inserindo dados sem ROW()

Como alternativa, você pode omitir a palavra-chave ROW(), porque o PostgreSQL entende implicitamente a estrutura composta:

INSERT INTO customers (name, contact)
VALUES ('Jane Smith', ('456 Oak Ave', 'Shelbyville', '67890'));

Os dois métodos chegam ao mesmo resultado, e a escolha depende principalmente do seu estilo de código e da clareza que você prefere.

6.3 Cuidados na inserção em massa

Quando você insere várias linhas, principalmente com tipos compostos, é importante garantir que os dados correspondam à estrutura definida. As operações de inserção em massa podem exigir uma formatação cuidadosa para evitar incompatibilidades de tipo ou erros de estrutura. Usar prepared statements ou queries parametrizadas ajuda a manter a consistência.


7. Acessando e consultando os campos de tipos compostos

Ler dados de colunas de tipo composto é tão simples quanto usar a notação de ponto para acessar os campos individuais. Esta seção detalha os vários métodos e as boas práticas para consultar tipos compostos.

7.1 Notação de ponto

Para acessar um campo de um tipo composto, use o formato column.field. Por exemplo:

SELECT name, contact.city
FROM customers
WHERE contact.zip = '12345';

Esta query retorna os nomes dos clientes e a parte da cidade das informações de contato de todos os clientes com o CEP ‘12345’.

7.2 Projeção dos campos compostos

Você também pode projetar o campo composto inteiro em uma query. Isso pode ser útil para aplicações que precisam do registro completo:

SELECT id, name, contact
FROM customers;

Em muitas bibliotecas cliente, o campo composto pode vir como um array ou como um objeto estruturado que a lógica da sua aplicação pode interpretar depois.

7.3 Usando tipos compostos em cláusulas WHERE

Os tipos compostos podem ser usados em cláusulas WHERE para filtrar os dados com base em um ou mais dos seus atributos:

SELECT *
FROM customers
WHERE (contact).city = 'Springfield' AND (contact).zip = '12345';

Os parênteses em volta de contact ajudam o PostgreSQL a interpretar corretamente o campo composto quando você usa a notação de ponto.

7.4 Exemplos da documentação oficial

A documentação oficial do PostgreSQL traz outros exemplos de uso dos tipos compostos. Um deles mostra como selecionar e manipular linhas que contêm colunas de tipo composto. Vale a pena revisar esses exemplos para entender melhor os padrões de uso na prática.


8. Atualizando os campos de tipos compostos

Os tipos compostos podem ser atualizados por inteiro ou campo a campo. Aqui vamos ver as diferentes abordagens para atualizar os campos compostos.

8.1 Atualizando o campo composto inteiro

Para atualizar o campo composto inteiro, basta atribuir um novo valor composto que respeite a estrutura do tipo:

UPDATE customers
SET contact = ('789 Pine Rd', 'Capital City', '54321')
WHERE id = 1;

Esta instrução substitui todas as informações de contato do cliente com id = 1.

8.2 Atualizando campos individuais

Se você precisa atualizar só um atributo específico de um tipo composto, pode fazer isso com a notação de ponto:

UPDATE customers
SET contact.city = 'Ogdenville'
WHERE id = 2;

Este comando muda só a parte city do campo composto do cliente com id = 2, deixando intacto o restante das informações de contato.

8.3 Cuidados na atualização

Atualizar campos compostos pode exigir atenção especial se o tipo composto for usado em várias tabelas ou funções. Garanta sempre que o novo valor respeite a estrutura e os tipos de dado definidos no tipo composto.


9. Tipos compostos em funções

Os tipos compostos ficam especialmente poderosos quando usados junto com as funções do PostgreSQL. Eles podem simplificar a interface da função encapsulando vários parâmetros relacionados ou retornando dados complexos em um único objeto composto.

9.1 Retornando tipos compostos

Um caso de uso comum é escrever uma função que retorna um tipo composto. Considere o exemplo a seguir, que lê o endereço de um cliente:

CREATE FUNCTION get_customer_address(customer_id INT) RETURNS address AS $$
BEGIN
    RETURN (SELECT contact FROM customers WHERE id = customer_id);
END;
$$ LANGUAGE plpgsql;

Chamar esta função retorna o tipo composto address, que depois pode ser desmontado nas aplicações cliente:

SELECT get_customer_address(1);

9.2 Recebendo tipos compostos como parâmetros

As funções também podem receber tipos compostos como argumentos. Por exemplo, uma função que atualiza o endereço de um cliente pode ficar assim:

CREATE FUNCTION update_customer_address(customer_id INT, new_address address) RETURNS VOID AS $$
BEGIN
    UPDATE customers
    SET contact = new_address
    WHERE id = customer_id;
END;
$$ LANGUAGE plpgsql;

Encapsulando vários parâmetros em um único argumento composto, a interface da função fica mais limpa e fácil de entender.

9.3 Operações complexas com tipos compostos

Os tipos compostos também servem para operações mais avançadas. Considere, por exemplo, uma função que trabalha com um tipo composto que representa números complexos. A documentação oficial do PostgreSQL traz um exemplo:

CREATE TYPE complex AS (
    r double precision,
    i double precision
);

CREATE FUNCTION complex_add(a complex, b complex) RETURNS complex AS $$
BEGIN
    RETURN (a.r + b.r, a.i + b.i);
END;
$$ LANGUAGE plpgsql;

SELECT complex_add((1.0, 2.0), (3.0, 4.0));

A função complex_add mostra como os tipos compostos podem encapsular operações com números complexos. Retornando um tipo composto, a função encapsula de forma organizada o resultado da soma.


10. Indexando os campos de tipos compostos

Os tipos compostos são uma ferramenta poderosa para organizar os dados, mas trazem alguns desafios na hora da indexação. O PostgreSQL não permite criar um índice diretamente sobre uma coluna de tipo composto. Em vez disso, você precisa criar índices funcionais sobre os campos individuais do tipo composto.

10.1 Criando índices funcionais

Para criar um índice sobre um campo de um tipo composto, use a seguinte sintaxe:

CREATE INDEX idx_customers_contact_city ON customers ((contact).city);

Neste comando, (contact).city é uma expressão que extrai o campo city da coluna composta contact. Este índice funcional acelera as queries que filtram ou ordenam pelo valor da cidade.

10.2 Indexando vários campos

Se você precisa otimizar queries que envolvem vários campos do tipo composto, pode criar vários índices:

CREATE INDEX idx_customers_contact_zip ON customers ((contact).zip);

Ter índices nos campos mais consultados pode reduzir drasticamente o tempo de resposta das queries, principalmente em conjuntos de dados grandes.

10.3 Impacto nas operações de escrita

É importante lembrar que os índices melhoram o desempenho das queries, mas podem adicionar custo às operações de escrita (INSERT, UPDATE, DELETE). Quando você atualiza os campos de um tipo composto, o PostgreSQL precisa atualizar os índices funcionais correspondentes. Como em toda estratégia de indexação, é um equilíbrio entre desempenho de leitura e custo de escrita.


11. Considerações de desempenho

Quando você projeta um schema com tipos compostos, é essencial pensar nas implicações de desempenho. Os tipos compostos podem deixar o schema mais claro, mas podem afetar o desempenho das queries se não forem usados com cuidado.

11.1 Desempenho das queries

Usar índices funcionais sobre os campos dos tipos compostos ajuda a reduzir os problemas de desempenho. Mas os desenvolvedores devem saber que:

  • Acessar campos aninhados com a notação de ponto pode impedir algumas otimizações da query se eles não estiverem indexados corretamente.
  • Expressões complexas que envolvem tipos compostos podem deixar a execução mais lenta se o motor do banco não conseguir otimizar bem o plano da query.

11.2 Desempenho de escrita

As operações de insert e update em tabelas com tipos compostos podem ter um pequeno custo extra por causa de:

  • A necessidade de montar ou desmontar a estrutura de dados composta.
  • O custo de atualizar os índices funcionais associados.

11.3 Memória e armazenamento

Os tipos compostos, por encapsularem vários campos, podem usar um pouco mais de memória do que valores escalares guardados separadamente. Mas a organização e a clareza que o schema ganha costumam justificar esse custo.

11.4 Benchmark e testes

Antes de levar tipos compostos para produção, é recomendável medir o desempenho com cargas de trabalho realistas. Os testes podem revelar gargalos inesperados e permitem ajustar a estratégia de indexação ou até, em alguns casos, repensar o uso dos tipos compostos em favor de tabelas normalizadas.


12. Boas práticas no uso de tipos compostos

Para tirar o máximo dos tipos compostos e evitar as armadilhas comuns, considere estas boas práticas:

12.1 Projete com fronteiras lógicas claras

Garanta que os campos agrupados em um tipo composto sejam logicamente relacionados. Evite criar tipos compostos que juntam dados sem relação entre si, porque isso gera confusão e dificulta a manutenção.

12.2 Use nomes de campo descritivos

Os nomes dos campos de um tipo composto devem ser autoexplicativos. Isso melhora a legibilidade do código e facilita a compreensão do schema por outros desenvolvedores e DBAs.

12.3 Documente o seu schema

Dada a flexibilidade dos tipos compostos, é essencial manter a documentação atualizada. Documente a finalidade de cada tipo composto e dos seus campos, incluindo as relações com as tabelas ou funções que os usam.

12.4 Aproveite os índices funcionais

Planeje e implemente índices funcionais sobre os campos de tipos compostos mais consultados. Esse passo é crucial para manter o desempenho das queries conforme os dados crescem.

12.5 Planeje a evolução do schema

Como alterar tipos compostos não é tão simples quanto alterar tabelas, planeje a evolução do schema desde o começo. Pense na possível necessidade de mudanças futuras e projete os seus tipos compostos para serem o mais flexíveis possível.

12.6 Teste bastante

Teste sempre o comportamento dos tipos compostos no seu ambiente de desenvolvimento antes de levá-los para produção. Isso inclui testes de desempenho, de correção e de qualquer caso limite que possa aparecer.


13. Cenários de uso avançados

Além dos padrões de uso básicos, os tipos compostos podem ser aplicados a cenários mais avançados. Esta seção mostra alguns desses casos.

13.1 Arrays de tipos compostos

O PostgreSQL suporta arrays de qualquer tipo de dado, inclusive tipos compostos. Esse recurso permite guardar vários valores compostos em uma única coluna. Por exemplo:

CREATE TABLE orders (
    order_id   SERIAL PRIMARY KEY,
    customer_id INT,
    items      address[]  -- Supondo que 'address' represente os endereços de entrega de vários itens
);

Trabalhar com arrays de tipos compostos exige cuidado no SQL, mas é uma forma poderosa de modelar relações um-para-muitos diretamente em uma única linha.

13.2 Aninhamento e recursão

Os tipos compostos podem ser aninhados uns nos outros. Isso é útil para modelar dados hierárquicos. Considere, por exemplo, um cenário em que uma organização tem vários departamentos, e cada departamento tem o seu próprio endereço:

CREATE TYPE department_address AS (
    street TEXT,
    city   TEXT,
    zip    CHAR(5)
);

CREATE TYPE department AS (
    name    TEXT,
    address department_address
);

CREATE TABLE organization (
    org_id       SERIAL PRIMARY KEY,
    org_name     TEXT NOT NULL,
    departments  department[]  -- Array de departamentos
);

Aninhar tipos compostos dessa forma permite modelar estruturas organizacionais complexas sem recorrer a joins em excesso.

13.3 Tipos compostos em views

As views também podem usar tipos compostos para apresentar uma visão simplificada e agregada dos dados. Por exemplo:

CREATE VIEW customer_details AS
SELECT id,
       name,
       contact.street AS street,
       contact.city AS city,
       contact.zip AS zip
FROM customers;

Nesta view, o campo composto contact é desmontado em colunas individuais. Essa abordagem é útil quando você quer oferecer uma interface que esconda a complexidade dos tipos compostos dos usuários finais ou das aplicações.

13.4 Combinando tipos compostos com JSON

O PostgreSQL tem um excelente suporte aos tipos de dado JSON e JSONB, mas os tipos compostos continuam sendo uma boa alternativa para dados estruturados que não precisam da flexibilidade do JSON. Em alguns casos você pode converter um tipo composto em JSON para a comunicação com o exterior:

SELECT row_to_json(contact) FROM customers;

Esta função converte o campo composto em um objeto JSON, juntando as vantagens dos tipos SQL estruturados com a interoperabilidade do JSON.

13.5 Operadores e funções personalizados

Usuários avançados podem definir operadores personalizados para trabalhar diretamente com tipos compostos. Por exemplo, você pode criar operadores que comparam valores compostos ou fazem contas com os campos dos tipos compostos. É um tópico avançado, mas mostra bem a extensibilidade do sistema de tipos do PostgreSQL.

13.6 Integração com outros recursos do PostgreSQL

Os tipos compostos podem ser combinados com muitos outros recursos do PostgreSQL, como herança, partições e foreign data wrappers. Essa integração permite construir soluções de banco de dados muito modulares e escaláveis. Por exemplo, você pode criar tabelas particionadas com colunas de tipo composto, garantindo que cada partição mantenha a mesma estrutura composta.


14. Comparação com conceitos parecidos em outros bancos de dados

Vale a pena comparar os tipos compostos do PostgreSQL com construções parecidas disponíveis em outros bancos de dados.

14.1 Os object types do Oracle

O Oracle suporta object types que oferecem funcionalidades parecidas. Mas a implementação dos tipos compostos do PostgreSQL costuma ser considerada mais leve e flexível, principalmente porque se integra naturalmente ao SQL e às linguagens procedurais como o PL/pgSQL.

14.2 SQL Server e os user-defined types (UDT)

O SQL Server oferece user-defined types (UDT), que permitem aos desenvolvedores criar estruturas de dados personalizadas. Já os tipos compostos do PostgreSQL são definidos diretamente em SQL, o que dá mais portabilidade e integração com as outras construções SQL.

14.3 Comparação com o NoSQL

No mundo NoSQL, bancos de documentos como o MongoDB permitem guardar documentos aninhados. Os tipos compostos do PostgreSQL oferecem parte dessa mesma funcionalidade, mas dentro de um banco relacional, com integridade transacional e a possibilidade de consultar com SQL.

14.4 Quando usar cada abordagem

A escolha entre tipos compostos e outras técnicas de modelagem de dados depende das necessidades da sua aplicação. Os tipos compostos brilham quando você precisa de um schema rígido, segurança de tipos e integração natural com as queries relacionais. São menos indicados quando os dados são muito variáveis ou não estruturados, cenários em que JSONB ou soluções NoSQL podem ser mais adequados.


15. Segurança e integridade dos dados

Integridade e segurança dos dados são fundamentais em qualquer design de banco de dados. Os tipos compostos, como qualquer outra estrutura de dados, devem ser usados de modo a garantir que os dados continuem consistentes e seguros.

15.1 Constraints e tipos compostos

Você pode aplicar constraints aos campos de tipos compostos de várias formas:

  • Constraints de domínio: defina um campo do tipo composto como pertencente a um domain que inclui constraints.
  • Check constraints: aplique check constraints no nível da tabela que façam referência a campos de um tipo composto.

Por exemplo, para garantir que um CEP (ZIP code) tenha sempre exatamente 5 caracteres, você pode definir:

CREATE DOMAIN zip_code AS CHAR(5)
CHECK (char_length(VALUE) = 5);

CREATE TYPE address AS (
    street TEXT,
    city   TEXT,
    zip    zip_code
);

Essa abordagem aproveita o mecanismo de domain do PostgreSQL para impor constraints aos campos dos tipos compostos.

15.2 Row-level security

As políticas de row-level security se aplicam às tabelas, contenham elas tipos compostos ou não. Mas fique atento: as políticas podem precisar levar em conta os campos compostos ao filtrar os dados.

15.3 Auditoria e log

Quando os tipos compostos são usados em funções ou stored procedures, considere registrar em log as mudanças nos campos compostos. Isso é especialmente importante em aplicações em que a integridade dos dados é crítica e as mudanças precisam ser auditadas.


16. Migração e evolução dos tipos compostos

Evoluir o schema do banco faz parte natural do desenvolvimento de software. Mas os tipos compostos podem criar dificuldades durante as migrações.

16.1 Estratégias para a evolução do schema

Como os tipos compostos não podem ser alterados com a mesma flexibilidade das tabelas, considere estas estratégias:

  • Versionamento: mantenha versões diferentes dos tipos compostos conforme o schema evolui. Por exemplo, você pode definir address_v1 e depois criar address_v2.
  • Apagar e recriar: em ambientes de desenvolvimento, pode ser aceitável apagar e recriar os tipos compostos. Em produção, isso precisa ser feito com extremo cuidado e com scripts de migração adequados.
  • Tabelas wrapper: se você prevê mudanças frequentes, considere usar tabelas wrapper com foreign keys em vez de tipos compostos. Isso dá mais flexibilidade, ao custo de mais complexidade.

16.2 Ferramentas e práticas de migração

Use ferramentas de migração (como Flyway, Liquibase ou scripts próprios) para gerenciar as mudanças nos tipos compostos de forma controlada. Testar bem em ambientes de staging é crucial antes de aplicar as migrações em produção.

16.3 Exemplo de cenário de migração

Imagine que você precisa adicionar um novo campo, state, ao seu tipo composto address. Uma migração possível envolveria:

  1. Criar um novo tipo composto address_new com o campo adicional.
  2. Alterar as tabelas para converter as colunas address existentes em address_new.
  3. Apagar o tipo composto antigo quando a migração estiver concluída.

O processo pode ser complexo, mas um bom planejamento e a automação reduzem os riscos ligados às mudanças de schema.


17. Armadilhas comuns e solução de problemas

Por mais poderosos que sejam, os tipos compostos podem dar problemas aos desenvolvedores. Estas são algumas armadilhas comuns e dicas para resolvê-las:

17.1 Incompatibilidade de tipos de dado

Garanta que os dados inseridos nas colunas de tipo composto correspondam exatamente à estrutura definida. Diferenças no número de campos ou nos tipos de dado podem causar erros em tempo de execução.

17.2 Dificuldades na indexação

Lembre-se de que não é possível indexar um tipo composto como um todo. Não criar índices funcionais nos campos mais consultados pode levar a um desempenho abaixo do ideal.

17.3 Custo nas funções

Quando os tipos compostos são muito usados em funções, garanta que o seu código PL/pgSQL esteja otimizado. Funções mal projetadas que manipulam tipos compostos podem adicionar custos desnecessários.

17.4 Tratamento de erros

Implemente um tratamento de erros robusto nas suas funções, principalmente nas conversões de tipos compostos ou nas migrações. Logs e mensagens de erro claras ajudam a diagnosticar os problemas rapidamente.

17.5 Divergências com a documentação

Na dúvida, volte sempre à documentação oficial do PostgreSQL. As divergências entre o que você entendeu e o comportamento documentado muitas vezes se resolvem relendo a fonte.


18. Desenvolvimentos futuros e propostas da comunidade

O PostgreSQL está em desenvolvimento constante, e a sua comunidade propõe o tempo todo melhorias nos recursos existentes, tipos compostos incluídos. Não se esperam mudanças radicais no curto prazo, mas vale a pena observar estas tendências:

18.1 Evolução do schema mais fácil

Existe uma discussão em andamento na comunidade sobre facilitar a alteração dos tipos compostos. Versões futuras do PostgreSQL podem trazer mecanismos mais flexíveis para alterar tipos compostos sem precisar apagá-los e recriá-los.

18.2 Mais opções de indexação

Com modelos de dados cada vez mais complexos, também há interesse em desenvolver estratégias de indexação mais sofisticadas, capazes de lidar automaticamente com os campos compostos, reduzindo o trabalho manual dos desenvolvedores.

18.3 Integração com novos tipos de dado

Com a popularização do JSONB e de outros tipos de dado semiestruturados, os tipos compostos podem evoluir para oferecer mais interoperabilidade com esses formatos. Isso pode incluir funções de conversão nativas ou abordagens híbridas que juntem as vantagens dos dados estruturados e semiestruturados.

18.4 Propostas da comunidade

Fique de olho nas listas de e-mail e nos fóruns da comunidade PostgreSQL para acompanhar as propostas mais recentes sobre tipos compostos. Participar da comunidade dá acesso antecipado aos recursos que estão por vir e às boas práticas.


19. Conclusão

Os tipos compostos do PostgreSQL são um recurso poderoso que permite aos desenvolvedores modelar estruturas de dados complexas do mundo real de forma limpa e organizada. Encapsulando dados relacionados em um único campo, os tipos compostos oferecem mais legibilidade, interfaces de função melhores e a possibilidade de uma integridade de dados maior. Mas, como todo recurso avançado, trazem os seus próprios desafios, principalmente na evolução do schema, na indexação e no ajuste de desempenho.

Neste guia vimos:

  • Introdução e definição: uma visão geral do que são os tipos compostos e por que são úteis.
  • Criação e uso: instruções detalhadas para criar tipos compostos e usá-los em definições de tabelas.
  • Manipulação dos dados: técnicas para inserir, atualizar e consultar os campos de tipos compostos.
  • Integração com funções: como os tipos compostos simplificam os parâmetros e os tipos de retorno das funções.
  • Indexação e desempenho: boas práticas para indexar campos compostos e cuidados para manter o desempenho.
  • Cenários avançados: casos de uso como arrays de tipos compostos, compostos aninhados e integração com JSON.
  • Análise comparativa: como os tipos compostos do PostgreSQL se comparam a construções parecidas em outros bancos de dados.
  • Segurança, integridade e migração: estratégias para garantir a integridade dos dados e lidar com as mudanças de schema.
  • Perspectivas: uma discussão sobre as melhorias esperadas e as propostas da comunidade sobre os tipos compostos.

Entendendo e aproveitando os tipos compostos, você consegue criar bancos de dados mais fáceis de manter, mais eficientes e com uma estrutura mais lógica. Seja uma aplicação pequena ou um sistema corporativo de grande porte, os tipos compostos podem ajudar a reduzir a complexidade e melhorar a integridade dos dados.

Artigos relacionados sobre PostgreSQL:


20. Referências


Apêndice: exemplos aprofundados e casos de uso

A. Modelando números complexos

Como vimos na documentação oficial, os tipos compostos podem representar construções matemáticas como os números complexos. Por exemplo:

CREATE TYPE complex AS (
    r double precision,
    i double precision
);

CREATE FUNCTION complex_add(a complex, b complex) RETURNS complex AS $$
BEGIN
    RETURN (a.r + b.r, a.i + b.i);
END;
$$ LANGUAGE plpgsql;

-- Somando dois números complexos:
SELECT complex_add((1.0, 2.0), (3.0, 4.0));

Este exemplo mostra o uso dos tipos compostos em operações aritméticas e mostra a flexibilidade do PostgreSQL no tratamento de tipos de dado personalizados.

B. Tipos compostos aninhados em estruturas organizacionais

Imagine modelar uma organização com departamentos e endereços aninhados:

CREATE TYPE department_address AS (
    street TEXT,
    city   TEXT,
    zip    CHAR(5)
);

CREATE TYPE department AS (
    name    TEXT,
    address department_address
);

CREATE TYPE organization AS (
    org_name     TEXT,
    departments department[]
);

CREATE TABLE organizations (
    id   SERIAL PRIMARY KEY,
    data organization
);

Neste cenário, uma organização pode ter vários departamentos, cada um com o seu próprio endereço. As queries sobre esses dados aninhados podem ser montadas com as poderosas construções SQL do PostgreSQL.

C. Usando tipos compostos em queries analíticas

Os tipos compostos podem ser úteis em contextos analíticos em que se processam dados agrupados. Considere uma tabela de pedidos que contém um tipo composto representando o endereço de entrega:

CREATE TABLE orders (
    order_id   SERIAL PRIMARY KEY,
    customer_id INT,
    shipping_address address,
    order_date TIMESTAMP
);

-- Uma query analítica que conta os pedidos por cidade:
SELECT shipping_address.city, COUNT(*) AS order_count
FROM orders
GROUP BY shipping_address.city;

Esta query aproveita a estrutura do tipo composto para agregar os dados por um atributo específico do endereço de entrega.

D. Combinando tipos compostos com window functions

Queries avançadas, como as que usam window functions, também funcionam sem problemas com tipos compostos. Por exemplo:

SELECT order_id,
       customer_id,
       shipping_address.city,
       RANK() OVER (PARTITION BY shipping_address.city ORDER BY order_date DESC) AS city_rank
FROM orders;

Aqui a window function classifica os pedidos dentro de cada cidade. Exemplos como este mostram que os tipos compostos não atrapalham as operações SQL complexas.


Considerações finais

Resumindo, os tipos compostos do PostgreSQL são uma ferramenta versátil que pode simplificar muito a forma como você projeta e usa o seu banco de dados. Eles incentivam um nível de abstração mais alto e um agrupamento mais lógico dos dados relacionados, bem alinhados com os princípios modernos de design de software.

Ao longo deste guia examinamos todos os aspectos dos tipos compostos: da criação e do uso aos cenários avançados, à otimização de desempenho e até às estratégias de migração. Existem desafios, principalmente na evolução do schema e na indexação, mas os ganhos em clareza, manutenção e alinhamento com o modelo de domínio da sua aplicação são consideráveis.

Conforme você continua a projetar e desenvolver os seus schemas, tenha em mente os trade-offs de qualquer decisão de design. Os tipos compostos não servem para tudo; em alguns casos uma abordagem mais normalizada pode ser a escolha certa. Mas, usados no lugar certo, oferecem uma solução elegante para encapsular dados complexos de um jeito natural e eficiente.

Este guia pretende ser ao mesmo tempo um material de estudo e um manual de referência. Experimente os tipos compostos no seu ambiente de desenvolvimento e consulte a documentação oficial do PostgreSQL para mais detalhes e atualizações.


Referências

  1. Documentação oficial do PostgreSQL: Row Types
  2. Documentação oficial do PostgreSQL: Indexes
  3. Documentação oficial do PostgreSQL: Data Types
  4. Discussões da comunidade sobre tipos compostos

Treinamentos

Se você quer dominar o PostgreSQL, a minha empresa oferece alguns treinamentos bem procurados:

Se a sua empresa precisa de um treinamento sob medida, fale com a gente.

Os treinamentos estão disponíveis remotamente e presencialmente.

Comments