PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Contrainte d'exclusion sur une colonne de chaîne de bits avec l'opérateur AND au niveau du bit

Comme votre modification a été clarifiée, vous avez installé l'extension btree_gist . Sans cela, l'exemple échouerait déjà à name WITH = .

CREATE EXTENSION btree_gist;

Les classes d'opérateurs installées par btree_gist couvrent de nombreux opérateurs. Malheureusement, le & l'opérateur n'en fait pas partie. Évidemment parce qu'il ne renvoie pas de boolean ce qui serait attendu d'un opérateur pour se qualifier.

Solution alternative

J'utiliserais une combinaison d'un index multi-colonnes b-tree (pour la vitesse) et un déclencheur Au lieu. Considérez cette démo, testée sur 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.

Devrait très bien fonctionner, en fait mieux que la contrainte d'exclusion, car la maintenance d'un index b-tree est moins chère qu'un index GiST. Et la recherche avec de base = les opérateurs devraient être plus rapides que les recherches hypothétiques avec le & opérateur.

Cette solution n'est pas aussi sûre qu'une contrainte d'exclusion, car les déclencheurs peuvent plus facilement être contournés - dans un déclencheur ultérieur sur le même événement par exemple, ou si le déclencheur est temporairement désactivé. Soyez prêt à exécuter des vérifications supplémentaires sur l'ensemble de la table si de telles conditions s'appliquent.

Condition plus complexe

L'exemple de déclencheur n'attrape que l'inversion de value . Comme vous l'avez précisé dans votre commentaire, vous avez en fait besoin d'une condition comme celle-ci :

IF EXISTS (
      SELECT 1 FROM t
      WHERE  name = NEW.name
      AND    value & NEW.value <> B'00000000'::bit(8)
      ) THEN

Cette condition est légèrement plus chère, mais peut toujours utiliser un index. L'index multi-colonnes ci-dessus fonctionnerait - si vous en avez besoin de toute façon. Ou, légèrement plus efficace, un simple index sur le nom :

CREATE INDEX t_name_idx ON t (name);

Comme vous l'avez commenté, il ne peut y avoir qu'un maximum de 8 lignes distinctes par name , moins en pratique. Cela devrait donc être rapide.

Performances INSERT ultimes

Si INSERT les performances sont primordiales, en particulier si de nombreuses tentatives d'INSERT échouent à la condition, vous pouvez faire plus :créer une vue matérialisée qui pré-agrège la value par name :

CREATE TABLE mv_t AS 
SELECT name, bit_or(value) AS value
FROM   t
GROUP  BY 1
ORDER  BY 1;

name est assuré d'être unique ici. J'utiliserais une PRIMARY KEY sur name pour fournir l'index que nous recherchons :

ALTER TABLE mv_t SET (fillfactor=90);

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

Ensuite, votre INSERT pourrait ressembler à ceci :

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 (...)

Le fillfactor n'est utile que si votre table reçoit beaucoup de mises à jour.

Mettre à jour les lignes de la vue matérialisée dans un TRIGGER AFTER INSERT OR UPDATE OF name, value OR DELETE pour le tenir à jour. Le coût des objets supplémentaires doit être mis en balance avec le gain. Dépend en grande partie de votre charge typique.