8.15. Tipos de dados matricial #

8.15.1. Declaração dos tipos de dados matriz
8.15.2. Entrada dos valores da matriz
8.15.3. Acesso às matrizes
8.15.4. Modificação de matrizes
8.15.5. Procura em matrizes
8.15.6. Sintaxe de entrada e de saída das matrizes

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.

8.15.1. Declaração dos tipos de dados matriz #

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.

8.15.2. Entrada dos valores da matriz #

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.

8.15.3. Acesso às matrizes #

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 limite-inferior:limite-superior 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:

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)

8.15.4. Modificação de matrizes #

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.

8.15.5. Procura em matrizes #

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)

Dica

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.

8.15.6. Sintaxe de entrada e de saída das matrizes #

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.

Dica

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.