O tipo de dados composto representa a estrutura de uma linha ou registro; é essencialmente apenas uma lista de nomes de campos e seus tipos de dados. O PostgreSQL permite que tipos de dados compostos sejam usados da mesma forma que os tipos de dados simples podem ser usados. Por exemplo, uma coluna de uma tabela pode ser declarada como sendo de um tipo de dados composto.
Aqui estão dois exemplos simples de definição de tipos de dados compostos:
CREATE TYPE complexo AS (
r double precision,
i double precision
);
CREATE TYPE item_inventário AS (
nome text,
id_fornecedor integer,
preco numeric
);
A sintaxe é comparável a do comando CREATE TABLE,
exceto por somente poderem ser especificados os nomes e tipos de dados
dos campos; no momento não pode ser incluída nenhuma restrição
(como NOT NULL).
Note que a palavra-chave AS é essencial;
sem ela, o sistema vai pensar que se trata de um tipo diferente de
comando CREATE TYPE, e vai mostrar erros de sintaxe
estranhos.
Após definir os tipos de dados, eles podem ser utilizados para criar tabelas:
CREATE TABLE estoque (
item item_inventário,
quantidade integer
);
INSERT INTO estoque VALUES (ROW('dado de pano', 42, 1.99), 1000);
ou funções:
CREATE FUNCTION preco_quantidade(item_inventário, integer) RETURNS numeric AS 'SELECT $1.preco * $2' LANGUAGE SQL; SELECT preco_quantidade(item, 10) FROM estoque;
Sempre que uma tabela é criada, um tipo de dados composto também é criado automaticamente, com o mesmo nome da tabela, para representar o tipo de dados da linha da tabela. Por exemplo, se tivesse sido escrito:
CREATE TABLE item_inventário (
nome text,
id_fornecedor integer REFERENCES fornecedores,
preco numeric CHECK (preco > 0)
);
então o mesmo tipo de dados composto item_inventário
mostrado acima passaria a existir como um subproduto, e poderia ser
usado como acima.
Note, no entanto, uma restrição importante da implementação corrente:
como nenhuma restrição está associada ao tipo de dados composto, as
restrições mostradas na definição da tabela não se
aplicam a valores do tipo de dados composto fora da tabela.
(Para contornar isso, deve ser criado um
domínio sobre o
tipo de dados composto e aplicadas as restrições desejadas,
como restrições CHECK do domínio.)
Para escrever um valor composto como uma constante literal, os valores dos campos devem ser colocados entre parênteses e separados por vírgulas. Podem ser colocadas aspas em torno de qualquer valor de campo, e isso deve ser feito se o campo contiver vírgulas ou parênteses. (Estão mostrados mais detalhes abaixo.) Assim, o formato geral de uma constante composta é o seguinte:
'(val1,val2, ... )'
Um exemplo é
'("dado de pano",42,1.99)'
que seria um valor válido para o tipo de dados
item_inventário definido acima.
Para tornar um campo nulo, não deve ser escrito nenhum caractere em
sua posição na lista.
Por exemplo, esta constante especifica um terceiro campo nulo:
'("dado de pano",42,)'
Se for desejada uma cadeia de caracteres vazia em vez de nulo, devem ser escritas duas aspas juntas, uma ao lado da outra:
'("",42,)'
Nesta constante, o primeiro campo é uma cadeia de caracteres vazia, não nula, e o terceiro campo é nulo.
(Essas constantes são, na verdade, apenas um caso especial das constantes de tipo genérico discutidas na Seção 4.1.2.7. A constante é inicialmente tratada como uma cadeia de caracteres e passada para a rotina de conversão de entrada do tipo de dados composto. Pode ser necessária uma especificação de tipo de dados explícita para informar para qual tipo de dados converter a constante.)
Também pode ser usada a sintaxe da expressão ROW
para construir valores compostos.
Geralmente isso é consideravelmente mais simples de usar
do que a sintaxe de literal cadeia de caracteres, porque não é
necessário se preocupar com várias camadas de aspas.
Esse método já foi usado acima:
ROW('dado de pano', 42, 1.99)
ROW('', 42, NULL)
Na realidade, a palavra-chave ROW é opcional, desde que a expressão tenha mais de um campo, portanto os exemplos acima podem ser simplificados para:
('dado de pano', 42, 1.99)
('', 42, NULL)
A sintaxe da expressão ROW é discutida com mais
detalhes na Seção 4.2.13.
Para acessar um campo de uma coluna composta, se escreve um ponto e o
nome do campo, parecido com selecionar um campo de um nome de tabela.
Na verdade, é tão parecido com o que é feito para selecionar a partir
de um nome de tabela, que é muitas vezes necessário usar parênteses
para não confundir o analisador.
Por exemplo, pode-se tentar selecionar alguns subcampos da nossa
tabela de exemplo estoque com algo como:
SELECT item.nome
FROM estoque
WHERE item.preco > 9.99;
ERRO: faltando entrada para tabela "item" na cláusula FROM
LINHA 1: SELECT item.nome
^
Isso não funciona, porque o nome item é considerado
um nome de tabela, e não um nome de coluna da tabela
estoque, segundo as regras de sintaxe da linguagem
SQL. Deve ser escrito assim:
SELECT (item).nome
FROM estoque
WHERE (item).preco > 9.99;
ou desta forma, se for necessário usar o nome da tabela também (por exemplo, em uma consulta com várias tabelas):
SELECT (estoque.item).nome
FROM estoque
WHERE (estoque.item).preco > 9.99;
Agora o objeto entre parênteses é interpretado corretamente como sendo
uma referência à coluna item, e então o subcampo
pode ser selecionado a partir dele.
Problemas de sintaxe semelhantes se aplicam sempre que se seleciona um campo de um valor composto. Por exemplo, para selecionar apenas um campo do resultado de uma função que retorna um valor composto, deve ser escrito algo como:
SELECT (minha_função(...)).campo FROM ...
Sem os parênteses extras, isso gera um erro de sintaxe.
O nome especial de campo * significa
“todos os campos”, conforme explicado na
Seção 8.16.5.
Aqui estão alguns exemplos da sintaxe adequada para inserir e atualizar colunas compostas. Primeiramente, inserir ou atualizar uma coluna inteira:
INSERT INTO minha_tabela (coluna_complexa) VALUES((1.1,2.2)); UPDATE minha_tabela SET coluna_complexa = ROW(1.1,2.2) WHERE ...;
O primeiro exemplo omite ROW, o segundo usa;
pode ser feito das duas maneiras.
Pode ser atualizado um subcampo de uma coluna composta individualmente:
UPDATE minha_tabela SET coluna_complexa.r = (coluna_complexa).r + 1 WHERE ...;
Note que aqui não precisamos (e de fato não podemos) colocar
parênteses ao redor do nome da coluna que aparece logo após
SET, mas precisamos de parênteses ao referenciar
a mesma coluna na expressão à direita do sinal de igual.
Podem ser especificados subcampos como destinos para o comando
INSERT também:
INSERT INTO minha_tabela (coluna_complexa.r, coluna_complexa.i) VALUES(1.1, 2.2);
Se não tivesse sido fornecido valores para todos os subcampos da coluna, os subcampos restantes teriam sido preenchidos com valores nulos.
Existem várias regras de sintaxe e comportamentos especiais associados a tipos de dados compostos em consultas. Essas regras fornecem atalhos úteis, mas podem ser confusas se não for conhecida a lógica por trás delas.
No PostgreSQL, uma referência a um nome de
tabela (ou alias) em uma consulta é de fato uma referência ao
valor composto da linha corrente da tabela.
Por exemplo, se tivéssemos a tabela
item_inventário conforme mostrado
acima, poderia ser escrito:
SELECT c FROM item_inventário c;
Essa consulta produz uma única coluna de valor composto, portanto podemos obter uma saída como:
c
--------------------------
("dado de pano",42,1.99)
(1 linha)
Note, no entanto, que os nomes simples correspondem aos nomes das
colunas antes dos nomes das tabelas, portanto este exemplo funciona
apenas por não haver uma coluna chamada c
nas tabelas da consulta.
A sintaxe comum de nome de coluna qualificada
nome_da_tabela.nome_da_coluna
pode ser compreendida como a aplicação da
seleção de campo ao valor
composto da linha corrente da tabela.
(Por razões de eficiência, não é na verdade implementado desta maneira.)
Quando se escreve
SELECT c.* FROM item_inventário c;
então, segundo o padrão SQL, devemos obter o conteúdo da tabela expandido em colunas separadas:
nome | id_fornecedor | preco
--------------+---------------+-------
dado de pano | 42 | 1.99
(1 linha)
como se a consulta fosse
SELECT c.nome, c.id_fornecedor, c.preco FROM item_inventário c;
O PostgreSQL irá aplicar este comportamento
de expansão a qualquer expressão de valor composto, embora como mostrado
acima, é necessário
escrever parênteses em torno do valor que é aplicado
.* sempre que não for um nome de tabela simples.
Por exemplo, se minha_função() for uma função que
retorna um tipo de dados composto com colunas a,
b e c, então
essas duas consultas produzem o mesmo resultado:
SELECT (minha_função(x)).* FROM alguma_tabela; SELECT (minha_função(x)).a, (minha_função(x)).b, (minha_função(x)).c FROM alguma_tabela;
O PostgreSQL lida com a expansão de
colunas transformando a primeira forma na segunda.
Então, nesse exemplo, minha_função()
seria chamada três vezes por linha com qualquer sintaxe.
Se for uma função cara, isso pode ser evitado, o que pode ser
feito usando uma consulta como:
SELECT m.* FROM alguma_tabela, LATERAL minha_função(x) AS m;
Colocar a função como LATERAL no
FROM impede que ela seja chamada mais de uma
vez por linha.
m.* ainda é expandida em
m.a, m.b, m.c, mas agora essas variáveis são
apenas referências à saída do FROM.
(A palavra-chave LATERAL é opcional nesse caso,
mas é mostrada para ficar claro que a função está obtendo
x de alguma_tabela.)
A sintaxe valor_composto.*
resulta numa expansão de coluna desse tipo de dados quando aparece
no nível superior de
SELECT lista de saída,
em RETURNING lista, em
INSERT/UPDATE/DELETE/MERGE,
em VALUES cláusula,
ou em construtor de linha.
Em todos os outros contextos (incluindo quando aninhado dentro de uma
dessas construções), anexar .* a um valor composto
não altera o valor, porque significa “todas as colunas”,
portanto o mesmo valor composto é produzido novamente.
Por exemplo, se a função alguma_função() aceita
um argumento de valor composto, essas consultas são as mesmas:
SELECT alguma_função(c.*) FROM item_inventário c; SELECT alguma_função(c) FROM item_inventário c;
Nos dois casos, a linha corrente de item_inventário
é passada para a função como um único argumento de valor composto.
Muito embora .* não faça nada nesses casos, usá-lo
é um bom estilo, porque deixa claro que se pretende um valor composto.
Em particular, o analisador considerará c em
c.* para se referir a um nome de tabela ou alias,
não a um nome de coluna, para que não haja ambiguidade; enquanto que
sem .*, não fica claro se c
significa um nome de tabela ou um nome de coluna, e de fato a
interpretação do nome da coluna será preferida se houver uma coluna
chamada c.
Outro exemplo demonstrando esses conceitos, é todas essas consultas significarem a mesma coisa:
SELECT * FROM item_inventário c ORDER BY c; SELECT * FROM item_inventário c ORDER BY c.*; SELECT * FROM item_inventário c ORDER BY ROW(c.*);
Todas essas cláusulas ORDER BY especificam o valor
composto da linha, resultando na classificação das linhas conforme
as regras descritas na Seção 9.25.6.
No entanto, se item_inventário contivesse
uma coluna chamada c, o primeiro caso seria
diferente dos demais, porque significaria classificar apenas por esta
coluna. Dados os nomes das colunas mostrados anteriormente, essas
consultas também são equivalentes as acima:
SELECT * FROM item_inventário c ORDER BY ROW(c.nome, c.id_fornecedor, c.preco); SELECT * FROM item_inventário c ORDER BY (c.nome, c.id_fornecedor, c.preco);
(O último caso usa um construtor de linha com a palavra-chave
ROW omitida.)
Outro comportamento sintático especial associado a valores compostos,
é poder ser usada a notação de função para
extrair um campo de um valor composto.
A maneira simples de explicar isso, é as notações
e campo(tabela)
serem intercambiáveis. Por exemplo, essas consultas são equivalentes:
tabela.campo
SELECT c.nome FROM item_inventário c WHERE c.preco > 1000; SELECT nome(c) FROM item_inventário c WHERE preco(c) > 1000;
Além disso, se tivermos uma função que aceita um único argumento de tipo de dados composto, podemos chamá-la com qualquer uma das notações. Essas consultas são todas equivalentes:
SELECT alguma_função(c) FROM item_inventário c; SELECT alguma_função(c.*) FROM item_inventário c; SELECT c.alguma_função FROM item_inventário c;
Essa equivalência entre notação de função e notação de campo, torna
possível usar funções em tipos de dados compostos para implementar
“campos computados”.
Uma aplicação usando a última consulta acima não precisaria estar
diretamente ciente de que alguma_função não é uma
coluna real da tabela.
Devido a este comportamento, não se recomenda dar a uma função que
recebe um único argumento de tipo de dados composto o mesmo nome de
qualquer um dos campos desse tipo de dados composto.
Se houver ambiguidade, a interpretação do nome do campo será
escolhida se for usada a sintaxe de nome de campo, enquanto a
função será escolhida se for usada a sintaxe de chamada de função.
No entanto, as versões do PostgreSQL
anteriores a 11 sempre escolhiam a interpretação do nome do campo,
a menos que a sintaxe da chamada exigisse que fosse uma chamada de
função. Uma maneira de forçar a interpretação da função em versões
mais antigas era qualificar o nome da função pelo esquema, ou seja,
escrever
.
esquema.função(valor_composto)
A representação textual externa de um valor composto consiste em
itens interpretados segundo as regras de conversão de
E/S para os tipos de dados de campo individuais, além de um adorno que
indica a estrutura composta.
O adorno consiste em parênteses (( e
)) ao redor de todo o valor, mais vírgulas
(,) entre os itens adjacentes.
O espaço em branco fora dos parênteses é ignorado, mas dentro dos
parênteses é considerado parte do valor do campo e pode ou não ser
significativo dependendo das regras de conversão de entrada para o
tipo de dados do campo.
Por exemplo, em
'( 42)'
o espaço em branco será ignorado se o tipo de dados do campo for inteiro, mas não se for de texto.
Conforme foi mostrado anteriormente, ao escrever um valor composto podem ser escritas aspas em torno de qualquer valor de campo individual. Isso deve ser feito se o valor do campo confundir o analisador de valor composto. Em particular, os campos que contêm parênteses, vírgulas, aspas ou contrabarras devem estar entre aspas. Para colocar aspas ou contrabarra em um valor de campo composto entre aspas, este caractere deve ser precedido por uma contrabarra. (Além disso, um par de aspas dentro de um valor de campo entre aspas é usado para representar um caractere aspas, de forma análoga às regras para apóstrofos em literais cadeias de caracteres no SQL.) Como alternativa, as aspas podem ser evitadas e usado o escape de contrabarra para proteger todos os caracteres de dados que, de outra forma, seriam considerados sendo sintaxe de tipo de dados composto.
Um valor de campo inteiramente vazio (sem caracteres entre as
vírgulas ou parênteses) representa um NULL.
Para escrever um valor que seja uma cadeia de caracteres vazia em vez
de NULL, deve ser escrito "".
A rotina de saída de tipo de dados composto coloca aspas nos valores de campo se forem cadeias de caracteres vazias, ou contiverem parênteses, vírgulas, aspas, contrabarras ou espaços em branco. (Fazer isso para espaços em branco não é essencial, mas ajuda na legibilidade.) Aspas e contrabarras incorporadas a valores de campo são duplicadas.
Lembre-se de que o que se escreve em um comando SQL
é interpretado primeiro como literal cadeia de caracteres e, depois,
como tipo de dados composto.
Isso dobra o número de contrabarras necessárias
(assumindo que esteja sendo usada a sintaxe de escape na cadeia de
caracteres).
Por exemplo, para inserir um campo text contendo aspas
e contrabarra em um valor composto, é necessário escrever:
INSERT ... VALUES ('("\"\\")');
O processador de literal cadeia de caracteres remove um nível de
contrabarras, de modo que o que chega ao analisador de valor composto
se parece com ("\"\\").
Por sua vez, a cadeia de caracteres alimentada para a rotina de entrada
do tipo de dados text se torna "\.
(Se estivéssemos trabalhando com um tipo de dados cuja rotina de
entrada também tratasse as contrabarras de forma especial, o tipo de dados
bytea por exemplo, poderíamos precisar de até oito
contrabarras no comando para obter uma contrabarra no campo composto
armazenado.)
Pode ser usada a delimitação por cifrão (veja a
Seção 4.1.2.4)
para evitar a necessidade de dobrar as contrabarras.
Geralmente é mais fácil trabalhar com a sintaxe do construtor
ROW do que com a sintaxe de literal composto ao
escrever valores compostos em comandos SQL.
Na sintaxe ROW, valores de campo individuais são
escritos da mesma forma como são escritos quando não são membros
de um valor composto.