Olhando para a documentação do postgres para alter table , parece que as restrições regulares podem ser marcadas como DEFERRABLE
(mais concretamente, INITIALLY DEFERRED
, que é o que me interessa).
Os índices também podem ser associados a uma restrição, desde que:
O índice não pode ter colunas de expressão nem ser um índice parcial
O que me leva a acreditar que atualmente não há como ter um índice único com condições, como:
CREATE UNIQUE INDEX unique_booking
ON public.booking
USING btree
(check_in, check_out)
WHERE booking_status = 1;
Ser INITIALLY DEFERRED
, ou seja, que a 'restrição' de exclusividade só será verificada ao final da transação (se SET CONSTRAINTS ALL DEFERRED;
for utilizada).
Minha suposição está correta e, em caso afirmativo, existe alguma maneira de alcançar o comportamento pretendido?
Obrigado
Um índice não pode ser adiado - não importa se é
UNIQUE
ou não, parcial ou não, apenas umaUNIQUE
restrição. Outros tipos de restrições (FOREIGN KEY
,PRIMARY KEY
,EXCLUDE
) também são adiáveis - mas nãoCHECK
restrições.Portanto, o índice parcial exclusivo (e a restrição implícita que ele implementa) será verificado em cada instrução (e, de fato, após cada inserção/atualização de linha na implementação atual), não no final da transação.
O que você pode fazer, se quiser implementar essa restrição como adiável, é adicionar mais uma tabela no design. Algo assim:
Com este design e supondo que
booking_status
tenha apenas 2 opções possíveis (0 e 1), você pode removê-lo inteiramente debooking
(se houver uma linha embooking_status
, é 1, se não for 0).Outro método seria (ab) usar uma
EXCLUDE
restrição:Testado em dbfiddle .
O que o acima faz:
A
CASE
expressão se tornaNULL
quandobooking_status
é nulo ou diferente de 1. Poderíamos escrever(CASE WHEN booking_status = 1 THEN TRUE END)
como(booking_status = 1 OR NULL)
se isso deixasse mais claro.As restrições exclusivas e de exclusão aceitam linhas em que uma ou mais das expressões é NULL. Portanto, ele atua como um índice filtrado com
WHERE booking_status = 1
.Todos os
WITH
operadores são=
assim que atua como umaUNIQUE
restrição.Esses dois combinados fazem com que a restrição atue como um índice exclusivo filtrado.
Mas é uma restrição e as
EXCLUDE
restrições podem ser adiadas.Uma melhoria do método acima (thnx para Denis Ryzhkov ) é usar uma restrição EXCLUDE parcial (filtrada). Usa menos espaço (da mesma forma que um índice parcial) e é adiável:
Testado em dbfiddle-2 .
Embora os anos dessa pergunta tenham se passado, gostaria de esclarecer para os falantes de espanhol, os testes foram feitos no Postgres:
A seguinte restrição foi adicionada a uma tabela de 1337 registros, onde o kit é a chave primária:
Isso cria uma chave primária padrão NOT DEFERRED para a tabela, portanto, ao tentar a próxima UPDATE, recebemos o erro:
No Postgres, executar um UPDATE para cada ROW verifica se a RESTRICTION ou CONSTRAINT foi atendida.
O CONSTRAINT IMMEDIATE agora é criado e cada instrução é executada separadamente:
Aqui o SI permite alterar a chave primária, pois executa toda a primeira frase completa (1328 linhas); mas apesar de estar em transação (BEGIN), o CONSTRAINT é validado imediatamente ao terminar cada frase sem ter feito COMMIT, portanto gera o erro ao executar o INSERT. Por fim criamos a CONSTRAINT DEFERRED faça o seguinte:
Se executarmos cada instrução do **Bloco 2**, cada sentença separadamente, nenhum erro é gerado para o INSERT, pois ele não valida, mas o COMMIT final é executado onde encontrar uma inconsistência.
Para informações completas em inglês sugiro que verifique os links:
Restrições SQL adiáveis em profundidade
NÃO DIFERÍVEL versus DIFERÍVEL INICIALMENTE IMEDIATO