Come chiarito dalla tua modifica, hai installato l'estensione btree_gist
. Senza di esso, l'esempio fallirebbe già in name WITH =
.
CREATE EXTENSION btree_gist;
Le classi dell'operatore installate da btree_gist
coprire molti operatori. Sfortunatamente, il &
operatore non è tra questi. Ovviamente perché non restituisce un boolean
che ci si aspetterebbe da un operatore per qualificarsi.
Soluzione alternativa
Userei una combinazione di un indice a più colonne b-tree (per la velocità) e un trigger invece. Considera questa demo, testata su 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.
-
L'estensione
btree_gist
è non richiesto in questo scenario. -
Ho limitato il trigger a INSERT o UPDATE di colonne pertinenti per efficienza.
-
Un vincolo di controllo non funzionerebbe. Cito il manuale su
CREATE TABLE
:Enfasi in grassetto la mia:
Dovrebbe funzionare molto bene, in realtà meglio del vincolo di esclusione, perché il mantenimento di un indice b-tree è più economico di un indice GiST. E la ricerca con =
di base gli operatori dovrebbero essere più veloci delle ipotetiche ricerche con &
operatore.
Questa soluzione non è sicura quanto un vincolo di esclusione, perché i trigger possono essere aggirati più facilmente, ad esempio in un trigger successivo sullo stesso evento o se il trigger è temporaneamente disabilitato. Preparati a eseguire controlli aggiuntivi sull'intero tavolo se si applicano tali condizioni.
Condizione più complessa
Il trigger di esempio rileva solo l'inversione di value
. Come hai chiarito nel tuo commento, in realtà hai bisogno di una condizione come questa:
IF EXISTS (
SELECT 1 FROM t
WHERE name = NEW.name
AND value & NEW.value <> B'00000000'::bit(8)
) THEN
Questa condizione è leggermente più costosa, ma può comunque utilizzare un indice. L'indice a più colonne dall'alto funzionerebbe, se ne hai comunque bisogno. Oppure, leggermente più efficiente, un semplice indice sul nome:
CREATE INDEX t_name_idx ON t (name);
Come hai commentato, possono esserci solo un massimo di 8 righe distinte per name
, meno in pratica. Quindi dovrebbe essere ancora veloce.
Prestazioni INSERT massime
Se INSERT
le prestazioni sono fondamentali, soprattutto se molti tentativi di INSERT non soddisfano la condizione, potresti fare di più:creare una vista materializzata che pre-aggregasse il value
per name
:
CREATE TABLE mv_t AS
SELECT name, bit_or(value) AS value
FROM t
GROUP BY 1
ORDER BY 1;
name
è garantito per essere unico qui. Userei una PRIMARY KEY
su name
per fornire l'indice che stiamo cercando:
ALTER TABLE mv_t SET (fillfactor=90);
ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name) WITH (fillfactor=90);
Quindi il tuo INSERT
potrebbe assomigliare a questo:
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 (...)
Il fillfactor
è utile solo se la tua tabella riceve molti aggiornamenti.
Aggiorna le righe nella vista materializzata in un TRIGGER AFTER INSERT OR UPDATE OF name, value OR DELETE
per mantenerlo aggiornato. Il costo degli oggetti aggiuntivi deve essere soppesato rispetto al guadagno. Dipende in gran parte dal tuo carico tipico.