PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Restrição de exclusão em uma coluna bitstring com operador AND bit a bit


Conforme sua edição esclareceu, você instalou a extensão btree_gist . Sem ele, o exemplo já falharia em name WITH = .
CREATE EXTENSION btree_gist;

As classes de operadores instaladas por btree_gist abranger muitos operadores. Infelizmente, o & operador não está entre eles. Obviamente porque não retorna um boolean que seria esperado de um operador para se qualificar.

Solução alternativa


Eu usaria uma combinação de um índice de várias colunas de árvore b (para velocidade) e um gatilho em vez de. Considere esta demonstração, testada no PostgreSQL 9.1 :
CREATE TABLE t (
  name text 
 ,value bit(8)
);

INSERT INTO t VALUES ('a', B'10101010'); 

CREATE INDEX t_name_value_idx ON t (name, value);

CREATE OR REPLACE FUNCTION trg_t_name_value_inversion_prohibited()
  RETURNS trigger AS
$func$
BEGIN
IF EXISTS (
     SELECT 1 FROM t
     WHERE (name, value) = (NEW.name, ~ NEW.value)  -- example: exclude inversion
     ) THEN

    RAISE EXCEPTION 'Your text here!';
END IF;

RETURN NEW;
END
$func$ LANGUAGE plpgsql;

CREATE TRIGGER insup_bef_t_name_value_inversion_prohibited
BEFORE INSERT OR UPDATE OF name, value  -- only involved columns relevant!
ON t
FOR EACH ROW
EXECUTE PROCEDURE trg_t_name_value_inversion_prohibited();

INSERT INTO t VALUES ('a', ~ B'10101010');  -- fails with your error msg.

Deve ter um desempenho muito bom, na verdade melhor do que a restrição de exclusão, porque a manutenção de um índice b-tree é mais barata que um índice GiST. E a pesquisa com = básico operadores devem ser mais rápidos do que pesquisas hipotéticas com o & operador.

Essa solução não é tão segura quanto uma restrição de exclusão, porque os gatilhos podem ser contornados mais facilmente - em um gatilho subsequente no mesmo evento, por exemplo, ou se o gatilho estiver desativado temporariamente. Esteja preparado para executar verificações extras em toda a mesa se tais condições se aplicarem.

Condição mais complexa


O gatilho de exemplo captura apenas a inversão de value . Como você esclareceu em seu comentário, você realmente precisa de uma condição como esta:
IF EXISTS (
      SELECT 1 FROM t
      WHERE  name = NEW.name
      AND    value & NEW.value <> B'00000000'::bit(8)
      ) THEN

Essa condição é um pouco mais cara, mas ainda pode usar um índice. O índice de várias colunas acima funcionaria - se você precisar dele de qualquer maneira. Ou, um pouco mais eficiente, um índice simples no nome:
CREATE INDEX t_name_idx ON t (name);

Como você comentou, só pode haver no máximo 8 linhas distintas por name , menos na prática. Portanto, isso ainda deve ser rápido.

Desempenho de INSERT final


Se INSERIR o desempenho é fundamental, especialmente se muitas tentativas de INSERT falharem na condição, você pode fazer mais:criar uma visualização materializada que pré-agregado valor por nome :
CREATE TABLE mv_t AS 
SELECT name, bit_or(value) AS value
FROM   t
GROUP  BY 1
ORDER  BY 1;

nome é garantido para ser único aqui. Eu usaria uma CHAVE PRIMÁRIA em nome para fornecer o índice que procuramos:
ALTER TABLE mv_t SET (fillfactor=90);

ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name) WITH (fillfactor=90);

Em seguida, seu INSERIR poderia ficar assim:
WITH i(n,v) AS (SELECT 'a'::text, B'10101010'::bit(8)) 
INSERT INTO t (name, value)
SELECT n, v
FROM   i
LEFT   JOIN mv_t m ON m.name = i.n
                  AND m.value & i.v <> B'00000000'::bit(8)
WHERE  m.n IS NULL;          -- alternative syntax for EXISTS (...)

O fator de preenchimento só é útil se sua tabela receber muitas atualizações.

Atualizar linhas na visualização materializada em um TRIGGER AFTER INSERT OR UPDATE OF name, value OR DELETE para mantê-lo atualizado. O custo dos objetos adicionais deve ser ponderado em relação ao ganho. Depende em grande parte da sua carga típica.