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.
-
~
é o operador de inversão .
-
A extensãobtree_gist
é não necessário neste cenário.
-
Restringi o gatilho para INSERT ou UPDATE de colunas relevantes para eficiência.
-
Uma restrição de verificação não funcionaria. Cito o manual sobreCREATE TABLE
:
Minha ênfase em negrito:
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.