8.17. Tipos de dados de intervalo #

8.17.1. Tipos de dados de intervalo e multi-intervalo nativos
8.17.2. Exemplos
8.17.3. Limites inclusivos e exclusivos
8.17.4. Intervalos infinitos (sem limites)
8.17.5. Entrada e saída de limite
8.17.6. Construção de intervalos e multi-intervalos
8.17.7. Tipos de dados de intervalo discretos
8.17.8. Definição de novos tipos de dados de intervalo
8.17.9. Indexação
8.17.10. Restrições em intervalos

Os tipos de dados de intervalo são tipos de dados que representam um intervalo de valores de algum tipo de elemento (chamado de subtipo do intervalo). Por exemplo, intervalos de timestamp podem ser usados para representar os intervalos de tempo em que uma sala de reunião está reservada. Nesse caso o tipo de dados é tsrange (abreviado de timestamp range), e timestamp é o subtipo. O subtipo deve ter uma ordenação completa, para ficar bem definido se os valores do elemento estão dentro, antes ou depois de um intervalo de valores.

Os tipos de dados de intervalo são úteis, porque representam muitos valores de elemento em um único valor de intervalo, e porque conceitos como intervalos sobrepostos podem ser expressos claramente. O uso de intervalos de tempo e data para fins de agendamento é o exemplo mais claro; mas faixas de preço, faixas de medição de um instrumento e assim por diante também podem ser úteis.

Cada tipo de dados de intervalo tem um tipo de dados multi-intervalo correspondente. Um multi-intervalo é uma lista ordenada de intervalos não contíguos, não vazios e não nulos. A maioria dos operadores de intervalo também trabalha com multi-intervalos, e eles têm algumas funções próprias.

8.17.1. Tipos de dados de intervalo e multi-intervalo nativos #

O PostgreSQL vem com os seguintes tipos de dados de intervalo nativos:

  • int4range — Intervalo de integer, int4multirange — Multi-intervalo correspondente

  • int8range — Intervalo de bigint, int8multirange — Multi-intervalo correspondente

  • numrange — Intervalo de numeric, nummultirange — Multi-intervalo correspondente

  • tsrange — Intervalo de timestamp without time zone, tsmultirange — Multi-intervalo correspondente

  • tstzrange — Intervalo de timestamp with time zone, tstzmultirange — Multi-intervalo correspondente

  • daterange — Intervalo de date, datemultirange — Multi-intervalo correspondente

Além desses, você pode definir seus próprios tipos de dados de intervalos de valores; veja CREATE TYPE para obter mais informações.

8.17.2. Exemplos #

CREATE TABLE reserva (sala int, período tsrange);
INSERT INTO reserva VALUES
    (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');

-- Contém
SELECT int4range(10, 20) @> 3;

-- Sobreposição
SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);

-- Obter o limite superior
SELECT upper(int8range(15, 25));

-- Computar a interseção
SELECT int4range(10, 20) * int4range(15, 25);

-- O intervalo está vazio?
SELECT isempty(numrange(1, 5));

Veja a Tabela 9.58 e a Tabela 9.60 para obter uma relação completa de operadores e funções para os tipos de dados de intervalo.

8.17.3. Limites inclusivos e exclusivos #

Todo intervalo não vazio tem dois limites, o limite inferior e o limite superior. Todos os pontos entre estes valores estão incluídos no intervalo. Um limite inclusivo significa que o próprio ponto de limite também está incluído no intervalo, enquanto um limite exclusivo significa que o ponto de limite não está incluído no intervalo.

Na forma textual de intervalo, o limite inferior inclusivo é representado por [, enquanto o limite inferior exclusivo é representado por (. Da mesma forma, o limite superior inclusivo é representado por ], enquanto o limite superior exclusivo é representado por ). (Veja a Seção 8.17.5 para obter mais detalhes.)

As funções lower_inc e upper_inc testam a inclusividade dos limites inferior e superior de um valor intervalo, respectivamente.

8.17.4. Intervalos infinitos (sem limites) #

O limite inferior de um intervalo pode ser omitido, significando que todos os valores menores que o limite superior estão incluídos no intervalo, por exemplo, (,3]. Da mesma forma, se o limite superior do intervalo for omitido, todos os valores maiores que o limite inferior estão incluídos no intervalo. Se os limites inferior e superior forem omitidos, todos os valores do tipo de dados do elemento são considerados estando no intervalo. Especificar um limite ausente como inclusivo é automaticamente convertido em exclusivo, por exemplo, [,] é convertido em (,). Pode-se pensar nesses valores ausentes como +/-infinito, mas são valores especiais do tipo de dados de intervalo, sendo considerados além dos valores +/-infinito de qualquer tipo de dados de elemento do intervalo.

Os tipos de dados de elementos com noção de infinito, podem usá-los como valores limites explícitos. Por exemplo, com intervalos carimbo de data/hora, [today,infinity) exclui o valor especial infinity do tipo de dados timestamp, enquanto [today,infinity] inclui, assim como [today,) e [today,].

As funções lower_inf e upper_inf testam para limites inferiores e superiores infinitos de um intervalo, respectivamente.

8.17.5. Entrada e saída de limite #

A entrada para um valor de intervalo deve seguir um dos seguintes padrões:

(limite-inferior,limite-superior)
(limite-inferior,limite-superior]
[limite-inferior,limite-superior)
[limite-inferior,limite-superior]
empty

Os parênteses ou colchetes indicam se os limites inferior e superior são exclusivos ou inclusivos, conforme descrito anteriormente. Note que o padrão final é empty, que representa um intervalo vazio (um intervalo que não contém pontos).

O limite-inferior pode ser uma cadeia de caracteres que seja uma entrada válida para o subtipo, ou vazia para indicar não haver limite inferior. Da mesma forma, limite-superior pode ser uma cadeia de caracteres que seja uma entrada válida para o subtipo, ou vazia para indicar não haver limite superior.

Cada valor limite pode ser delimitado usando caracteres " (aspas). Isso é necessário se o valor limite contiver parênteses, colchetes, vírgulas, aspas ou contrabarras, porque estes caracteres seriam considerados parte da sintaxe de intervalo. Para colocar aspas ou contrabarra em um valor limite entre aspas, preceda-o com uma contrabarra. (Além disso, é usado um par de aspas dentro de um valor limite entre aspas para representar um caractere aspas, de forma análoga às regras para apóstrofos em literais cadeia de caracteres do SQL.) Como alternativa, pode ser evitado usar aspas usando o escape de contrabarra para proteger todos os caracteres de dados que, de outra forma, seriam considerados sendo sintaxe de intervalo. Além disso, para escrever um valor limite que é uma cadeia de caracteres vazia, deve ser escrito "", porque não escrever nada significa um limite infinito.

São permitidos espaços em branco antes e depois do valor do intervalo, mas qualquer espaço em branco entre os parênteses ou colchetes é considerado sendo parte do valor do limite inferior ou superior. (Dependendo do tipo de dados de elemento, isso pode ou não ser significativo.)

Nota

Essas regras são muito semelhantes àquelas para escrever valores de campo em literais do tipo de dados composto. Veja a Seção 8.16.6 para obter mais informações.

Exemplos:

-- inclui o 3, não inclui o 7 e inclui todos os pontos intermediários
SELECT '[3,7)'::int4range;

-- não inclui o 3 nem o 7, mas inclui todos os pontos intermediários
SELECT '(3,7)'::int4range;

-- inclui apenas o único valor 4
SELECT '[4,4]'::int4range;

-- não inclui nenhum ponto (normalizado para 'empty')
SELECT '[4,4)'::int4range;

A entrada para multi-intervalos são chaves ({ e }) contendo zero ou mais intervalos válidos, separados por vírgulas. São permitidos espaços em branco entre os colchetes e vírgulas. Isso serve para lembrar a sintaxe das matrizes, embora os multi-intervalos sejam muito mais simples: eles têm apenas uma dimensão, não havendo necessidade de colocar seu conteúdo entre delimitadores. (Os limites de seus intervalos podem ser delimitados usando apóstrofos, como acima.)

Exemplos:

SELECT '{}'::int4multirange;
SELECT '{[3,7)}'::int4multirange;
SELECT '{[3,7), [8,9)}'::int4multirange;

8.17.6. Construção de intervalos e multi-intervalos #

Cada tipo de dados de intervalo tem uma função construtora com o mesmo nome do tipo de dados de intervalo. Usar a função construtora é frequentemente mais conveniente do que escrever uma constante literal de intervalo, pois evita a necessidade de delimitadores extras nos valores limites. A função construtora aceita dois ou três argumentos. A forma de dois argumentos constrói um intervalo na forma padrão (limite inferior inclusivo, limite superior exclusivo), enquanto a forma de três argumentos constrói um intervalo com limites da forma especificada pelo terceiro argumento. O terceiro argumento deve ser uma das cadeias de caracteres (), (], [), ou []. Por exemplo:

-- A forma completa é: limite inferior, limite superior e
-- argumento de texto indicando inclusão/exclusão dos limites.
SELECT numrange(1.0, 14.0, '(]');

-- Se o terceiro argumento for omitido, será assumido '[)'.
SELECT numrange(1.0, 14.0);

-- Embora aqui seja especificado '(]', na saída o valor será convertido
-- para a forma canônica, já que int8range é um tipo de dados de intervalo
-- discreto (veja abaixo).
SELECT int8range(1, 14, '(]');

-- Usar NULL para qualquer um dos limites faz com que
-- o intervalo seja ilimitado nesse lado.
SELECT numrange(NULL, 2.2);

Cada tipo de dados de intervalo também tem um construtor multi-intervalo com o mesmo nome do tipo de dados multi-intervalo. A função construtora recebe zero ou mais argumentos, sendo todos intervalos do tipo de dados apropriado. Por exemplo:

SELECT nummultirange();
SELECT nummultirange(numrange(1.0, 14.0));
SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));

8.17.7. Tipos de dados de intervalo discretos #

Um intervalo discreto é aquele cujo tipo de dados do elemento tem um passo bem definido, como integer ou date. Nesses tipos de dados, dois elementos podem ser considerados adjacentes quando não há valores válidos entre eles. Isso contrasta com intervalos contínuos, onde sempre (ou quase sempre) é possível identificar outros valores de elementos entre dois valores dados. Por exemplo, um intervalo do tipo de dados numeric é contínuo, assim como um intervalo do tipo de dados timestamp. (Muito embora timestamp tenha precisão limitada e, portanto, teoricamente possa ser tratado como discreto, mas é melhor considerá-lo como contínuo, porque o tamanho do passo normalmente não é de interesse.)

Outra forma de pensar sobre um tipo de dados de intervalo discreto, é haver uma ideia clara de um valor próximo ou anterior, para cada valor de elemento. Sabendo disso, é possível fazer a conversão entre representações inclusivas e exclusivas dos limites de um intervalo, escolhendo o valor do elemento seguinte ou anterior, em vez do originalmente fornecido. Por exemplo, os intervalos [4,8] e (3,9) sobre o tipo de dados inteiro denotam o mesmo conjunto de valores; mas isso não se dá para um intervalo sobre o tipo de dados numeric.

Um tipo de dados de intervalo discreto deve ter uma função de estruturação canônica que esteja ciente do tamanho do passo desejado para o tipo de dados do elemento. A função de estruturação canônica é responsável pela conversão de valores equivalentes do tipo de dados de intervalo, para terem representações idênticas, em particular limites inclusivos ou exclusivos de modo consistente. Se não for especificada uma função de estruturação canônica, intervalos com formatação diferente sempre serão tratados como não sendo iguais, mesmo que na realidade possam representar o mesmo conjunto de valores.

Os tipos de dados de intervalo nativos int4range, int8range e daterange usam uma forma canônica que inclui o limite inferior e exclui o limite superior; ou seja, [). No entanto, os tipos de dados de intervalo definidos pelo usuário podem usar outras convenções.

8.17.8. Definição de novos tipos de dados de intervalo #

Os usuários podem definir seus próprios tipos de dados de intervalo. O motivo mais comum para fazer isso é usar intervalos sobre subtipos não fornecidos entre os tipos de dados de intervalo nativos. Por exemplo, para definir um novo tipo de dados de intervalo do subtipo de dados float8:

CREATE TYPE floatrange AS RANGE (
    subtype = float8,
    subtype_diff = float8mi
);

SELECT '[1.234, 5.678]'::floatrange;

  floatrange
---------------
 [1.234,5.678]
(1 linha)

Como float8 não possui passo significativo, não foi definida uma função de estruturação canônica nesse exemplo.

Quando é definido um intervalo próprio, é obtido automaticamente um tipo de dados de multi-intervalo correspondente.

Definir um tipo de dados de intervalo próprio também permite especificar uma classe ou agrupamento de operadores de árvore-B de subtipo diferente para serem usados, de modo a alterar a ordem de classificação que determina quais valores se enquadram em um determinado intervalo.

Se o subtipo for considerado tendo valores discretos, em vez de contínuos, o comando CREATE TYPE deve especificar uma função canonical. A função de estruturação canônica usa um valor de intervalo de entrada e deve retornar um valor de intervalo equivalente que pode ter limites e formatação diferentes. A saída canônica para dois intervalos que representam o mesmo conjunto de valores, por exemplo, os intervalos de inteiros [1, 7] e [1, 8), devem ser idênticas. Não importa qual representação será escolhida para ser a canônica, desde que dois valores equivalentes com formatação diferente sejam sempre mapeados para o mesmo valor com a mesma formatação. Além de ajustar o formato de limites inclusivos/exclusivos, uma função de estruturação canônica pode arredondar valores de limite, caso o tamanho do passo desejado seja maior que o subtipo consegue armazenar. Por exemplo, um tipo de dados de intervalo sobre timestamp pode ser definido tendo um tamanho de passo de uma hora, caso em que a função de estruturação canônica precisaria arredondar os limites que não fossem múltiplos de uma hora, ou talvez lance um erro em vez disso.

Além disso, todo tipo de dados de intervalo destinado a ser usado com índices GiST ou SP-GiST deve definir uma diferença de subtipo de dados, ou função subtype_diff. (O índice ainda vai funcionar mesmo sem a função subtype_diff, mas provavelmente será consideravelmente menos eficiente do que com uma função de diferença fornecida.) A função de diferença de subtipo recebe dois valores de entrada do subtipo e retorna sua diferença (ou seja, X menos Y), representada como um valor do tipo de dados float8. No exemplo acima, a função float8mi, subjacente ao operador minus regular float8, pode ser usada; mas para qualquer outro subtipo, alguma conversão de tipo de dados seria necessária. Algum pensamento criativo sobre como representar diferenças como números também pode ser necessário. Na maior extensão possível, a função subtype_diff deve concordar com a ordem de classificação implícita da classe de operadores e agrupamento selecionados; ou seja, seu resultado deve ser positivo sempre que seu primeiro argumento for maior que o segundo, conforme a ordem de classificação.

Um exemplo menos simplificado da função subtype_diff é:

CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;

CREATE TYPE timerange AS RANGE (
    subtype = time,
    subtype_diff = time_subtype_diff
);

SELECT '[11:10, 23:00]'::timerange;
      timerange
---------------------
 [11:10:00,23:00:00]
(1 linha)

Veja CREATE TYPE para obter mais informações sobre a criação de tipos de dados de intervalo.

8.17.9. Indexação #

Podem ser criados índices GiST e SP-GiST para colunas de tabela de tipos de dados de intervalo. Os índices GiST também podem ser criados para colunas de tabela de tipos de dados de multi-intervalo. Por exemplo, para criar um índice GiST:

CREATE INDEX idx_reserva ON reserva USING GIST (período);

Um índice GiST ou SP-GiST em intervalos pode acelerar consultas envolvendo estes operadores de intervalo: =, &&, <@, @>, <<, >>, -|-, &< e &>. Um índice GiST em multi-intervalos pode acelerar consultas envolvendo o mesmo conjunto de operadores multi-intervalos. Um índice GiST em intervalos e um índice GiST em multi-intervalos também podem acelerar consultas envolvendo estes operadores de intervalo de tipo de dados cruzado de intervalo para multi-intervalo e de multi-intervalo para intervalo, de forma correspondente: &&, <@, @>, <<, >>, -|-, &< e &>. Veja a Tabela 9.58 para obter mais informações.

Além disso, podem ser criados índices árvore-B e hash para colunas de tabela de tipos de dados de intervalo. Para estes tipos de índice, basicamente a única operação de intervalo útil é a de igualdade. Existe uma ordenação de árvore-B definida para valores de intervalo, com operadores < e > correspondentes, mas a ordenação é bastante arbitrária e geralmente não é útil no mundo real. O suporte à árvore-B e ao hash dos tipos de dados de intervalo destina-se, principalmente, a permitir a classificação e o hash internamente nas consultas, em vez da criação de índices verdadeiros.

8.17.10. Restrições em intervalos #

Embora UNIQUE seja uma restrição natural para valores escalares, geralmente não é adequado para tipos de dados de intervalo. Em vez disso, uma restrição de exclusão é geralmente mais apropriada (veja CREATE TABLE ... CONSTRAINT ... EXCLUDE). As restrições de exclusão permitem a especificação de restrições como não sobreposição em um tipo de dados de intervalo. Por exemplo:

CREATE TABLE reserva (
    período tsrange,
    EXCLUDE USING GIST (período WITH &&)
);

Essa restrição impede que quaisquer valores sobrepostos existam na tabela ao mesmo tempo:

INSERT INTO reserva VALUES
    ('[2010-01-01 11:30, 2010-01-01 15:00)');
INSERT 0 1

INSERT INTO reserva VALUES
    ('[2010-01-01 14:45, 2010-01-01 15:45)');
ERRO:  valor-chave conflitante viola a restrição de exclusão "reserva_período_excl"
DETALHE:  A chave ("período")=(["2010-01-01 14:45:00","2010-01-01 15:45:00"))
          está em conflito com a chave existente
          ("período")=(["2010-01-01 11:30:00","2010-01-01 15:00:00")).

Pode ser usada a extensão btree_gist para definir restrições de exclusão em tipos de dados escalares simples, que podem ser combinadas com exclusões de intervalo para máxima flexibilidade. Por exemplo, após a instalação do btree_gist, a seguinte restrição irá rejeitar intervalos sobrepostos somente se os números das salas de reunião forem iguais:

CREATE EXTENSION btree_gist;
CREATE TABLE reserva_sala (
    sala    text,
    período tsrange,
    EXCLUDE USING GIST (sala WITH =, período WITH &&)
);

INSERT INTO reserva_sala VALUES
    ('123A', '[2010-01-01 14:00, 2010-01-01 15:00)');
INSERT 0 1

INSERT INTO reserva_sala VALUES
    ('123A', '[2010-01-01 14:30, 2010-01-01 15:30)');
ERRO:  valor-chave conflitante viola a restrição de exclusão "reserva_sala_sala_período_excl"
DETALHE:  A chave (sala, "período")=(123A, ["2010-01-01 14:30:00","2010-01-01 15:30:00"))
          está em conflito com a chave existente
          (sala, "período")=(123A, ["2010-01-01 14:00:00","2010-01-01 15:00:00")).

INSERT INTO reserva_sala VALUES
    ('123B', '[2010-01-01 14:30, 2010-01-01 15:30)');
INSERT 0 1

SELECT * FROM reserva_sala;

 sala |                    período
------+-----------------------------------------------
 123A | ["2010-01-01 14:00:00","2010-01-01 15:00:00")
 123B | ["2010-01-01 14:30:00","2010-01-01 15:30:00")
(2 linhas)