Estou tentando modelar a colocação de peças em uma placa de circuito. Sem nenhuma restrição significativa, meu esquema básico se parece com isso:
create table part (
part_id bigserial primary key,
name text not null,
width double precision not null,
height double precision not null
);
create table board (
board_id bigserial primary key,
width double precision not null,
height double precision not null
);
create table board_part (
board_id bigint not null references board,
part_id bigint not null references part,
position point not null
);
( SQL Fiddle , Visualização )
Para b
e b2
qualquer board_part
s, desejo impor as seguintes restrições:
b
encontra-se no tabuleiro:box(b.position, point(b.part.width,b.part.height)) <@ box(point(0,0), point(b.board.width,b.board.height))
b
eb2
não se sobreponham se estiverem no mesmo tabuleiro:b.board_id != b2.board_id or not (box(b.position, point(b.part.width,b.part.height)) && box(b2.position, point(b2.part.width,b2.part.height)))
Como posso conseguir isso (sem muita duplicação de dados)? Mudar o esquema é bom.
Aqui está minha melhor tentativa (SQL Fiddle) , inspirando-se na resposta de Erwin à minha pergunta anterior . Ele impõe as restrições que eu queria, mas tem muitos dados duplicados na board_part
tabela. Imagino que poderia escrever uma função para preencher os campos board_width
, board_height
, part_width
e part_height
automaticamente, mas ainda parece errado ter tantos dados duplicados por aí. Além disso, digitar nos campos width
/ height
parece um hack.
Resposta básica
Sugiro o tipo geométrico
box
e uma restrição de exclusão (Postgres 9.2+ ). Deve ser a solução perfeita para o seu problema. Ele cria implicitamente um índice GiST que também oferece suporte a determinadas consultas.Combine-o com igualdade
board_id
para permitir várias placas em uma única tabela. Você precisará do módulo adicionalbtree_gist
. Uma vez por banco de dados:Para limitar as peças a uma placa, adicione uma
CHECK
restrição . Você precisa da caixa confinante do tabuleiro naboard_part
mesa (redundante). Poderia ficar assim:&&
.. operador "overlaps"<@
.. operador "contido"A desvantagem: você precisa das dimensões de cada peça e caixa na tabela de forma
board_part
redundante.Resposta avançada
Para evitar armazenamento redundante, gosto mais dessa nova ideia:
IMMUTABLE
funções falsas que retornam a caixa para um id e criam restrições sobre essas funções. Suas tabelas podem se contentar apenas com as colunas de seu design original.Fiz um teste completo para verificar se funciona.
esquema completo
Tabelas básicas:
IMMUTABLE
funções:Substitua
public
pelo esquema real de suas tabelas.No Postgres 9.6 ou posterior, adicione
PARALLEL SAFE
. Ver:Tabela principal:
Obviamente, se você atualizar as dimensões para um
board_id
oupart_id
em uso, anulará parcialmente o índice e/ou a restrição. Você precisaria recriar qualquer índice ou restrição com base na promessa de que o valor de retorno daIMMUTABLE
função nunca muda. Ver:No entanto, você pode evitar essa operação cara com gatilhos para atualizar apenas as linhas afetadas:
Gatilhos
part_id = 0
é uma linha especial na tabelapart
com tamanho 0. Necessária para o gatilho.Dados de teste:
Teste
Estes devem gerar exceções se os gatilhos e restrições fizerem seu trabalho:
db<>fiddle aqui
Old sqlfiddle