O PostgreSQL permite que as colunas de uma tabela sejam definidas como matrizes (arrays) multidimensionais de comprimento variável. Podem ser criadas matrizes de qualquer tipo de dados base interno ou definido pelo usuário, tipo de dados de enumeração, composto, de intervalo ou domínio.
Para ilustrar o uso dos tipos de dados matriz, é criada a tabela abaixo:
CREATE TABLE sal_emp (
nome text,
salario_semanal integer[],
agenda text[][]
);
Conforme visto, o tipo de dados matriz é identificado anexando
colchetes ([]) ao nome do tipo de dados dos
elementos da matriz.
O comando acima cria uma tabela chamada
sal_emp com uma coluna do tipo de dados
text (nome),
uma matriz unidimensional do tipo de dados integer
(salario_semanal), representando o salário
semanal do empregado, e uma matriz bidimensional do tipo de dados
text (agenda), representando
a agenda semanal do empregado.
A sintaxe do comando CREATE TABLE permite
especificar o tamanho exato da matriz, como, por exemplo:
CREATE TABLE jogo_da_velha (
tabuleiro integer[3][3]
);
Entretanto, a implementação corrente ignora quaisquer limites de tamanho de matriz especificados, ou seja, o comportamento é o mesmo das matrizes sem o tamanho especificado.
A implementação corrente também não impõe o número declarado de dimensões.
As matrizes de um determinado tipo de elemento são todas consideradas
do mesmo tipo, independentemente do seu tamanho ou número de dimensões.
Portanto, declarar o tamanho da matriz ou o número de dimensões no
comando CREATE TABLE é apenas documentação;
não afeta o comportamento em tempo de execução.
Pode ser usada uma sintaxe alternativa para a criação de matrizes
unidimensionais, em conformidade com o padrão SQL,
empregando a palavra-chave ARRAY.
Então, salario_semanal poderia ter sido
definido como:
salario_semanal integer ARRAY[4],
ou, sem especificar o tamanho da matriz:
salario_semanal integer ARRAY,
Entretanto, como foi mencionado anteriormente, o PostgreSQL não impõe a restrição de tamanho em nenhum caso.
Para escrever um valor matriz como constante literal, os valores
dos elementos devem ser envoltos por chaves ({})
e separados por vírgulas (Quem conhece C pode ver
que não é diferente da sintaxe da linguagem C para
inicializar estruturas). Podem ser colocadas aspas (")
em torno de qualquer valor de elemento, sendo obrigatório caso o
elemento contenha vírgulas ou chaves (abaixo são mostrados mais
detalhes). Portanto, o formato geral de uma constante matriz é o
seguinte:
'{ val1 delim val2 delim ... }'
onde delim é o caractere delimitador para
o tipo de dados, conforme registrado na sua entrada em
pg_type.
Entre os tipos de dados padrão fornecidos na distribuição do
PostgreSQL, todos usam virgula
(,), exceto o tipo de dados box que
usa ponto e vírgula (;) como caractere delimitador.
Cada val é uma constante do tipo de dados
do elemento da matriz, ou uma submatriz.
Um exemplo de uma constante matriz é:
'{{1,2,3},{4,5,6},{7,8,9}}'
Esta constante é uma matriz bidimensional, 3 por 3, formada por três submatrizes de inteiros.
Para definir um elemento de uma constante matriz como
NULL, deve ser escrito NULL
para o valor do elemento. (Qualquer forma alternativa em maiúsculas ou
minúsculas de NULL serve.) Se for desejado
especificar o valor cadeia de caracteres “NULL”,
então deve-se colocar aspas em seu redor.
(Esses tipos de constantes matriz 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 cadeia de caracteres e passada para a rotina de conversão de entrada de matriz. Pode ser necessária uma especificação de tipo de dados explícita.)
Agora podemos mostrar alguns comandos INSERT:
INSERT INTO sal_emp
VALUES ('Bill',
'{10000, 10000, 10000, 10000}',
'{{"reunião", "almoço"}, {"treinamento", "apresentação"}}');
INSERT INTO sal_emp
VALUES ('Carol',
'{20000, 25000, 25000, 25000}',
'{{"café da manhã", "consultoria"}, {"reunião", "almoço"}}');
O resultado das duas inserções anteriores fica assim:
SELECT * FROM sal_emp;
nome | salario_semanal | agenda
-------+---------------------------+--------------------------------------------------
Bill | {10000,10000,10000,10000} | {{reunião,almoço},{treinamento,apresentação}}
Carol | {20000,25000,25000,25000} | {{"café da manhã",consultoria},{reunião,almoço}}
(2 linhas)
As matrizes multidimensionais devem ter tamanhos correspondentes para cada dimensão. Uma incompatibilidade causa erro, por exemplo:
INSERT INTO sal_emp
VALUES ('Bill',
'{10000, 10000, 10000, 10000}',
'{{"reunião", "almoço"}, {"reunião"}}');
ERRO: matriz mal formada: "{{"reunião", "almoço"}, {"reunião"}}"
LINHA 4: '{{"reunião", "almoço"}, {"reunião"}}');
^
DETALHE: Matrizes multidimensionais devem ter submatrizes com dimensões correspondentes.
Também pode ser usada a sintaxe do construtor ARRAY:
INSERT INTO sal_emp
VALUES ('Bill',
ARRAY[10000, 10000, 10000, 10000],
ARRAY[['reunião', 'almoço'], ['treinamento', 'apresentação']]);
INSERT INTO sal_emp
VALUES ('Carol',
ARRAY[20000, 25000, 25000, 25000],
ARRAY[['café da manhã', 'consultoria'], ['reunião', 'almoço']]);
Note que os elementos da matriz são constantes ou expressões
SQL comuns; por exemplo, literais cadeias de
caracteres estão envoltos por apóstrofos, em vez de aspas, como
seria em um literal matriz.
A sintaxe do construtor ARRAY é discutida mais
detalhadamente na Seção 4.2.12.
Agora podemos efetuar algumas consultas na tabela. Primeiro, será mostrado como acessar um único elemento da matriz. Esta consulta mostra os nomes dos empregados para os quais o pagamento foi diferente na segunda semana:
SELECT nome
FROM sal_emp
WHERE salario_semanal[1] <> salario_semanal[2];
nome ------- Carol (1 linha)
Os números dos índices da matriz são escritos entre colchetes.
Por padrão, o PostgreSQL utiliza a
convenção de numeração baseada em 1 para matrizes, ou seja,
uma matriz com n elementos começa com
array[1] e termina com
array[.
n]
Esta consulta mostra o pagamento da terceira semana de todos os empregados:
SELECT salario_semanal[3] FROM sal_emp;
salario_semanal
-----------------
10000
25000
(2 linhas)
Também é possível acessar fatias retangulares arbitrárias da matriz,
ou submatrizes.
Uma fatia da matriz é especificada escrevendo
para uma ou mais dimensões da matriz. Por exemplo, esta consulta
mostra o primeiro item na agenda do Bill para os primeiros dois dias
da semana:
limite-inferior:limite-superior
SELECT agenda[1:2][1:1] FROM sal_emp WHERE nome = 'Bill';
agenda
---------------------------
{{reunião},{treinamento}}
(1 linha)
Se alguma dimensão for escrita como fatia, ou seja, contiver dois
pontos, todas as dimensões são tratadas como fatias.
Qualquer dimensão que tenha apenas um único número (sem dois pontos)
é tratada como sendo de 1 ao número especificado.
Por exemplo, [2] é tratada como
[1:2], como nesse exemplo:
SELECT agenda[1:2][2] FROM sal_emp WHERE nome = 'Bill';
agenda
-----------------------------------------------
{{reunião,almoço},{treinamento,apresentação}}
(1 linha)
Para evitar confusão com o caso sem fatias, é melhor usar a sintaxe
de fatias para todas as dimensões, por exemplo,
[1:2][1:1], e não [2][1:1].
É possível omitir limite-inferior e/ou
limite-superior de um especificador de
fatia; o limite ausente é substituído pelo limite inferior ou superior
dos índices da matriz. Por exemplo:
SELECT agenda[:2][2:] FROM sal_emp WHERE nome = 'Bill';
agenda
---------------------------
{{almoço},{apresentação}}
(1 linha)
SELECT agenda[:][1:1] FROM sal_emp WHERE nome = 'Bill';
agenda
---------------------------
{{reunião},{treinamento}}
(1 linha)
Uma expressão de índice de matriz retorna nulo se a própria matriz
ou qualquer uma das expressões de índice forem nulas.
Além disso, é retornado nulo se um índice estiver fora dos limites da
matriz (este caso não gera erro).
Por exemplo, se agenda tiver atualmente as dimensões
[1:3][1:2] então fazer uma referência a
agenda[3][3] retorna nulo.
Da mesma forma, uma referência de matriz com número errado de índices
retorna o valor nulo, em vez de erro.
Da mesma forma, uma expressão de fatia de matriz retorna nulo se a própria matriz ou qualquer uma das expressões de índice forem nulas. No entanto, em outros casos, como selecionar uma fatia de matriz que está inteiramente fora dos limites da matriz corrente, a expressão de fatia retorna uma matriz vazia (zero-dimensional), em vez de nula. (Isso não corresponde ao comportamento sem fatias, sendo feito por motivos históricos.) Se a fatia solicitada se sobrepuser parcialmente aos limites da matriz, ela será silenciosamente reduzida apenas à região de sobreposição, em vez de retornar nulo.
As dimensões correntes de qualquer valor matriz podem ser recuperadas
usando a função array_dims:
SELECT array_dims(agenda) FROM sal_emp WHERE nome = 'Carol';
array_dims ------------ [1:2][1:2] (1 linha)
A função array_dims produz um resultado do tipo
de dados text, conveniente para as pessoas lerem, mas
talvez inconveniente para os programas.
As dimensões também podem ser obtidas usando as funções
array_upper e array_lower,
que retornam o limite superior e inferior de uma dimensão de matriz
especificada, respectivamente:
SELECT array_upper(agenda, 1) FROM sal_emp WHERE nome = 'Carol';
array_upper
-------------
2
(1 linha)
A função array_length retorna o comprimento de uma
dimensão da matriz especificada:
SELECT array_length(agenda, 1) FROM sal_emp WHERE nome = 'Carol';
array_length
--------------
2
(1 linha)
A função cardinality retorna o número total de
elementos em uma matriz em todas as dimensões.
É na verdade o número de linhas que uma chamada à função
unnest produziria:
SELECT cardinality(agenda) FROM sal_emp WHERE nome = 'Carol';
cardinality
-------------
4
(1 linha)
Um valor matriz pode ser inteiramente substituído usando
UPDATE sal_emp SET salario_semanal = '{25000,25000,27000,27000}'
WHERE nome = 'Carol';
ou usando a expressão sintática ARRAY
UPDATE sal_emp SET salario_semanal = ARRAY[25000,25000,27000,27000]
WHERE nome = 'Carol';
Também pode ser atualizado um único elemento da matriz
UPDATE sal_emp SET salario_semanal[4] = 15000
WHERE nome = 'Bill';
ou atualizar por fatia:
UPDATE sal_emp SET salario_semanal[1:2] = '{27000,27000}'
WHERE nome = 'Carol';
As sintaxes de fatia com limite-inferior
e/ou limite-superior omitido também pode
ser usada, mas apenas para atualizar um valor de matriz que não seja NULL
ou de dimensão zero (caso contrário, não há limite de índice existente
para substituir).
Um valor matriz armazenado pode ser ampliado fazendo atribuição a
elementos ainda não presentes.
Quaisquer posições entre os elementos anteriormente presentes e os
novos elementos atribuídos são preenchidos com nulos.
Por exemplo, se a matriz minha_matriz tiver
atualmente 4 elementos, terá 6 elementos após uma atualização que
atribui um valor a minha_matriz[6];
minha_matriz[5] conterá nulo.
No momento o aumento dessa forma é permitido apenas para matrizes
unidimensionais, não sendo permitido para matrizes multidimensionais.
A definição de índices permite a criação de matrizes que não usam
índices baseados em 1.
Por exemplo, pode ser definido minha_matriz[-2:7]
para criar uma matriz com valores de índices na faixa -2 a 7.
Novos valores matriz também podem ser construídos usando o operador
de concatenação ||:
SELECT ARRAY[1,2] || ARRAY[3,4];
?column?
-----------
{1,2,3,4}
(1 linha)
SELECT ARRAY[5,6] || ARRAY[[1,2],[3,4]];
?column?
---------------------
{{5,6},{1,2},{3,4}}
(1 linha)
O operador de concatenação permite que um único elemento seja inserido
no início ou no final de uma matriz unidimensional. Também aceita duas
matrizes N-dimensionais, ou uma matriz
N-dimensional e outra
N+1-dimensional.
Quando um único elemento é inserido no início ou no final de uma matriz unidimensional, o resultado é uma matriz com o mesmo índice de limite inferior do operando da matriz. Por exemplo:
SELECT array_dims(1 || '[0:1]={2,3}'::int[]);
array_dims ------------ [0:2] (1 linha)
SELECT array_dims(ARRAY[1,2] || 3);
array_dims ------------ [1:3] (1 linha)
Quando duas matrizes com número igual de dimensões são concatenadas, o resultado retém o índice do limite inferior da dimensão externa do operando à esquerda. O resultado é uma matriz contendo cada elemento do operando esquerdo seguido por cada elemento do operando direito. Por exemplo:
SELECT array_dims(ARRAY[1,2] || ARRAY[3,4,5]);
array_dims ------------ [1:5] (1 linha)
SELECT array_dims(ARRAY[[1,2],[3,4]] || ARRAY[[5,6],[7,8],[9,0]]);
array_dims ------------ [1:5][1:2] (1 linha)
Quando uma matriz N-dimensional é inserida
no início ou no final de uma matriz
N+1-dimensional, o resultado é semelhante
ao caso acima.
Cada submatriz N-dimensional é, essencialmente, um elemento da
dimensão externa da matriz N+1-dimensional. Por exemplo:
SELECT array_dims(ARRAY[1,2] || ARRAY[[3,4],[5,6]]);
array_dims ------------ [1:3][1:2] (1 linha)
Uma matriz também pode ser construída usando as funções
array_prepend, array_append,
ou array_cat.
As duas primeiras aceitam apenas matrizes unidimensionais, mas
array_cat aceita matrizes multidimensionais.
Alguns exemplos:
SELECT array_prepend(1, ARRAY[2,3]);
array_prepend
---------------
{1,2,3}
(1 linha)
SELECT array_append(ARRAY[1,2], 3);
array_append
--------------
{1,2,3}
(1 linha)
SELECT array_cat(ARRAY[1,2], ARRAY[3,4]);
array_cat
-----------
{1,2,3,4}
(1 linha)
SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);
array_cat
---------------------
{{1,2},{3,4},{5,6}}
(1 linha)
SELECT array_cat(ARRAY[5,6], ARRAY[[1,2],[3,4]]);
array_cat
---------------------
{{5,6},{1,2},{3,4}}
(1 linha)
Nos casos simples, o operador de concatenação discutido acima é preferível ao uso direto dessas funções. No entanto, como o operador de concatenação está sobrecarregado para atender aos três casos, há situações em que o uso de uma das funções é útil para evitar ambiguidade. Por exemplo, considere:
SELECT ARRAY[1, 2] || '{3, 4}'; -- o literal sem tipo de dados é tomado como sendo uma matriz
?column?
-----------
{1,2,3,4}
SELECT ARRAY[1, 2] || '7'; -- assim como este
ERRO: matriz mal formada: "7"
LINHA 1: SELECT ARRAY[1, 2] || '7';
^
DETALHE: Valor da matriz deve iniciar com "{" ou dimensão.
SELECT ARRAY[1, 2] || NULL; -- como também o NULL puro
?column?
----------
{1,2}
(1 linha)
SELECT array_append(ARRAY[1, 2], NULL); -- isso pode ter tido significado
array_append
--------------
{1,2,NULL}
Nos exemplos acima, o analisador vê uma matriz de inteiros em um lado
do operador de concatenação e uma constante de tipo de dados
indeterminado no outro.
A heurística usada para resolver o tipo de dados da constante é assumir
ser do mesmo tipo de dados que a outra entrada do operador —
nesse caso, uma matriz de inteiros.
Assim, presume-se que o operador de concatenação representa a função
array_cat, e não array_append.
Quando esta é a escolha errada, pode ser corrigida convertendo a
constante para o tipo de dados do elemento da matriz; mas o uso explícito
de array_append pode ser uma solução preferível.
Para procurar um valor em uma matriz, cada valor deve ser verificado. Isso pode ser feito manualmente, se for conhecido o tamanho da matriz. Por exemplo:
SELECT * FROM sal_emp WHERE salario_semanal[1] = 10000 OR
salario_semanal[2] = 10000 OR
salario_semanal[3] = 10000 OR
salario_semanal[4] = 10000;
No entanto, isso se torna entediante rapidamente para matrizes grandes, e não é útil se o tamanho da matriz for desconhecido. Um método alternativo está descrito na Seção 9.25. A consulta acima pode ser substituída por:
SELECT * FROM sal_emp WHERE 10000 = ANY (salario_semanal);
Além disso, podem ser encontradas as linhas onde a matriz tem todos os valores iguais a 10000 com:
SELECT * FROM sal_emp WHERE 10000 = ALL (salario_semanal);
A função generate_subscripts pode ser usada como
alternativa. Por exemplo:
SELECT * FROM
(SELECT salario_semanal,
generate_subscripts(salario_semanal, 1) AS s
FROM sal_emp) AS foo
WHERE salario_semanal[s] = 10000;
Esta função está descrita na Tabela 9.70.
Também pode ser realizada procura em uma matriz usando o operador
&&, que verifica se o operando esquerdo
se sobrepõe ao operando direito. Por exemplo:
SELECT * FROM sal_emp WHERE salario_semanal && ARRAY[10000];
Este e outros operadores de matriz estão descritos na Seção 9.19. Podem ser acelerados por um índice apropriado, conforme descrito na Seção 11.2.
Também podem ser procurados valores específicos em uma matriz usando
as funções array_position e
array_positions. A primeira função retorna o
índice da primeira ocorrência de um valor em uma matriz; a última
retorna uma matriz com os índices de todas as ocorrências do valor na
matriz. Por exemplo:
SELECT array_position(ARRAY['dom','seg','ter','qua','qui','sex','sab'], 'seg');
array_position
----------------
2
(1 linha)
SELECT array_positions(ARRAY[1, 4, 3, 1, 3, 4, 2, 1], 1);
array_positions
-----------------
{1,4,8}
(1 linha)
Matrizes não são conjuntos; procurar por elementos específicos em uma matriz pode ser um sinal de projeto incorreto do banco de dados. Considere o uso de uma tabela a parte com uma linha para cada item que seria um elemento da matriz. Isso será mais fácil de procurar e, provavelmente, será melhor dimensionado para um grande número de elementos.
A representação textual externa de um valor matriz consiste em itens
interpretados segundo as regras de conversão de E/S
para o tipo de dados do elemento da matriz, mais os adornos que
indicam a estrutura da matriz. Esses adornos consistem em chaves
({ e }) em torno do valor
matriz, mais os caracteres delimitadores entre os itens adjacentes.
O caractere delimitador é geralmente a vírgula (,),
mas pode ser outro: é determinado pela definição de
typdelim para o tipo de dados do elemento da matriz.
Entre os tipos de dados padrão fornecidos na distribuição do
PostgreSQL, todos usam vírgula, exceto o
tipo de dados box, que usa ponto e vírgula
(;).
Em uma matriz multidimensional cada dimensão (linha, plano, cubo,
etc.) recebe seu próprio nível de chaves, e devem ser escritos
delimitadores entre entidades de chaves adjacentes do mesmo nível.
A rotina de saída de matriz coloca aspas em torno dos valores dos
elementos se eles forem cadeias de caracteres vazias, contiverem
chaves, caracteres delimitadores, aspas, barras invertidas ou
espaços em branco, ou corresponderem à palavra NULL.
Aspas e contrabarras incorporadas aos valores dos elementos recebem
o escape de contrabarra. No caso dos tipos de dados numéricos é seguro
assumir que as aspas nunca vão estar presentes, mas para tipos de
dados textuais deve-se estar preparado para lidar tanto com a presença,
quanto com a ausência, das aspas.
Por padrão, o limite inferior do valor do índice de cada dimensão da
matriz é definido como 1.
Para representar matrizes com outros limites inferiores, os intervalos
do índice da matriz podem ser especificados explicitamente antes de
escrever o conteúdo da matriz.
Este adorno consiste em colchetes ([]) em torno
dos limites inferior e superior de cada dimensão da matriz, com um
caractere delimitador de dois pontos (:) entre eles.
O adorno de dimensão da matriz é seguido pelo sinal de igual
(=). Por exemplo:
SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2
FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;
e1 | e2 ----+---- 1 | 6 (1 linha)
A rotina de saída da matriz inclui as dimensões explícitas em seu resultado somente quando houver um ou mais limites inferiores diferentes de 1.
Se o valor escrito para um elemento for NULL
(em qualquer forma alternativa de letras maiúsculas e minúsculas),
o elemento será considerado NULL.
A presença de aspas ou contrabarras desativa este comportamento, e
permite ser inserido o valor do literal cadeia de caracteres
“NULL”. Além disso, para manter a compatibilidade com versões do
PostgreSQL anteriores à 8.2, o parâmetro
de configuração array_nulls pode ser definido
como off para suprimir o reconhecimento de
NULL como nulo.
Conforme mostrado anteriormente, ao escrever um valor matriz podem
ser colocadas aspas em torno de qualquer elemento individual da matriz.
Isso deve ser feito se o valor do elemento puder,
de alguma forma, confundir o analisador de valor matriz.
Por exemplo, os elementos contendo chaves, vírgulas (ou o caractere
delimitador do tipo de dados), aspas, contrabarras, ou espaços em
branco na frente ou atrás, devem estar entre aspas.
Cadeias de caracteres vazias e cadeias de caracteres que correspondam
à palavra NULL também devem estar entre aspas.
Para colocar aspas ou contrabarras no valor entre aspas do elemento
da matriz, eles devem ser precedidos por uma contrabarra.
Como alternativa, pode-se evitar aspas e usar o escape de contrabarra
para proteger todos os caracteres de dados que, de outra forma,
são considerados sendo sintaxe de matriz.
Podem ser adicionados espaços em branco antes do abre chaves ou após o fecha chaves. Também podem ser adicionados espaços em branco antes ou depois de qualquer item individual cadeia de caracteres. Em todos esses casos, os espaços em branco são ignorados. Entretanto, espaços em branco dentro de elementos entre aspas, ou envoltos nos dois lados por caracteres de um elemento que não são espaços em branco, não são ignorados.
A sintaxe do construtor ARRAY (consulte a
Seção 4.2.12) é geralmente mais
fácil de trabalhar do que a sintaxe de literal de matriz ao escrever
valores de matriz em comandos SQL. Em ARRAY,
valores de elementos individuais são escritos da mesma forma que
são escritos quando não são membros de uma matriz.