sqlpostgresqloverlapsranges

O operador OVERLAPS no SQL: cruzamento de periodos e deteccao de conflitos

Como usar OVERLAPS para verificar se dois periodos de tempo se cruzam e encontrar conflitos de reservas e turnos no PostgreSQL.

2 min de leituraReferencesql · postgresql · overlaps · ranges · dates

OVERLAPS e um operador infixo do padrao SQL que responde a uma unica pergunta: dois periodos de tempo se cruzam? Ele dispensa a comparacao manual de quatro datas e e ideal para encontrar conflitos em reservas, turnos e assinaturas.

Sintaxe basica

Cada periodo e um par entre parenteses: (start, end). A expressao devolve true quando os intervalos compartilham algum instante.

SELECT (DATE '2024-03-01', DATE '2024-03-10')
    OVERLAPS (DATE '2024-03-08', DATE '2024-03-15')  AS does_overlap;  -- true

SELECT (DATE '2024-03-01', DATE '2024-03-10')
    OVERLAPS (DATE '2024-03-10', DATE '2024-03-20')  AS does_overlap;  -- false

O segundo exemplo devolve false, e esse e o ponto-chave a internalizar: os limites sao tratados como um intervalo semiaberto [start, end). O fim de um periodo tocar o inicio de outro nao conta como cruzamento, exatamente o que voce quer para turnos em sequencia.

  • Cada operando e um par (inicio, fim).
  • Aceita date, timestamp, time e ate um par (instante, interval).
  • Se o inicio for maior que o fim, o PostgreSQL os troca silenciosamente.

Encontrar conflitos de reservas

Suponha que as reservas estejam ligadas a pedidos. Queremos qualquer pedido cujo periodo se cruze com uma nova solicitacao de 5 a 8 de marco:

SELECT o.id, o.user_id
FROM orders o
WHERE (o.created_at, o.created_at + INTERVAL '3 days')
   OVERLAPS (TIMESTAMP '2024-03-05', TIMESTAMP '2024-03-08');

Para encontrar cada par de periodos sobrepostos dentro de uma mesma tabela, fazemos um self-join e usamos o1.id < o2.id para descartar duplicatas e a comparacao de uma linha com ela mesma:

SELECT o1.id AS first_id, o2.id AS second_id
FROM orders o1
JOIN orders o2
  ON o1.user_id = o2.user_id
 AND o1.id < o2.id
 AND (o1.created_at, o1.created_at + INTERVAL '1 hour')
  OVERLAPS (o2.created_at, o2.created_at + INTERVAL '1 hour');

O equivalente com comparacao de datas

OVERLAPS e acucar sintatico sobre a condicao classica. Dois periodos se cruzam se e somente se:

-- a_start < b_end AND b_start < a_end
WHERE a_start < b_end
  AND b_start < a_end

Essa forma e indispensavel quando OVERLAPS e inconveniente ou inexistente — por exemplo no MySQL, que nao tem esse operador. A mesma busca de conflitos:

SELECT o1.id, o2.id
FROM orders o1
JOIN orders o2
  ON o1.user_id = o2.user_id
 AND o1.id < o2.id
 AND o1.created_at < o2.created_at + INTERVAL '1 hour'
 AND o2.created_at < o1.created_at + INTERVAL '1 hour';

Pegadinha: um erro comum e escrever <= em vez de <. Com < estrito, periodos adjacentes (fim = inicio) nao sao tratados como conflito, igual ao OVERLAPS. Se voce realmente quer que o toque conte como cruzamento (por exemplo, para cobertura continua), use <= — mas ai o comportamento diverge do OVERLAPS.

Os tipos range como alternativa

O PostgreSQL tem tipos de faixa dedicados (tsrange, daterange, int4range) e um operador de sobreposicao &&. Le-se melhor e, ao contrario de OVERLAPS, pode ser indexado com GiST.

SELECT o.id
FROM orders o
WHERE tsrange(o.created_at, o.created_at + INTERVAL '3 days')
   && tsrange(TIMESTAMP '2024-03-05', TIMESTAMP '2024-03-08');

A grande vantagem sao as restricoes de exclusao: o proprio banco garante que os periodos nunca se cruzem, sem condicoes de corrida na aplicacao. Por exemplo, voce pode proibir reservar um funcionario para dois turnos ao mesmo tempo:

ALTER TABLE employees
  ADD COLUMN shift tsrange;

ALTER TABLE employees
  ADD CONSTRAINT no_shift_conflict
  EXCLUDE USING gist (id WITH =, shift WITH &&);

Qualquer tentativa de inserir um turno sobreposto para o mesmo id falha no nivel do banco. O ClickHouse nao tem nem OVERLAPS nem tipos range, entao la voce recorre a condicao manual a_start < b_end AND b_start < a_end.

Resumindo: para uma checagem pontual no WHERE use OVERLAPS; para portabilidade, a comparacao manual; e para indexacao mais garantias rigidas de integridade, os tipos range com EXCLUDE.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador