Como aclaró su edición, instaló la extensión btree_gist
. Sin él, el ejemplo ya fallaría en name WITH =
.
CREATE EXTENSION btree_gist;
Las clases de operadores instaladas por btree_gist
cubrir muchos operadores. Desafortunadamente, el &
el operador no está entre ellos. Obviamente porque no devuelve un boolean
que se esperaría de un operador para calificar.
Solución alternativa
Usaría una combinación de un índice de varias columnas de árbol B (para velocidad) y un gatillo en cambio. Considere esta demostración, probada en 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.
-
La extensión
btree_gist
es no requerido en este escenario. -
Restringí el disparador para INSERTAR o ACTUALIZAR columnas relevantes para la eficiencia.
-
Una restricción de verificación no funcionaría Cito el manual sobre
CREATE TABLE
:Énfasis en negrita mío:
Debería funcionar muy bien, en realidad mejor que la restricción de exclusión, porque el mantenimiento de un índice de árbol b es más económico que un índice GiST. Y la búsqueda con =
básico los operadores deberían ser más rápidos que las búsquedas hipotéticas con &
operador.
Esta solución no es tan segura como una restricción de exclusión, porque los disparadores se pueden eludir más fácilmente, por ejemplo, en un disparador posterior en el mismo evento, o si el disparador se deshabilita temporalmente. Esté preparado para realizar comprobaciones adicionales en toda la tabla si se aplican dichas condiciones.
Condición más compleja
El disparador de ejemplo solo detecta la inversión de value
. Como aclaraste en tu comentario, en realidad necesitas una condición como esta:
IF EXISTS (
SELECT 1 FROM t
WHERE name = NEW.name
AND value & NEW.value <> B'00000000'::bit(8)
) THEN
Esta condición es un poco más costosa, pero aún puede usar un índice. El índice de varias columnas de arriba funcionaría, si lo necesita de todos modos. O, un poco más eficiente, un índice simple en el nombre:
CREATE INDEX t_name_idx ON t (name);
Como comentaste, solo puede haber un máximo de 8 filas distintas por name
, menos en la práctica. Así que esto aún debería ser rápido.
Máximo rendimiento de INSERCIÓN
Si INSERT
el rendimiento es primordial, especialmente si muchos intentos de INSERCIÓN fallan en la condición, podría hacer más:crear una vista materializada que agregue previamente value
por name
:
CREATE TABLE mv_t AS
SELECT name, bit_or(value) AS value
FROM t
GROUP BY 1
ORDER BY 1;
name
está garantizado que será único aquí. Usaría una PRIMARY KEY
en name
para proporcionar el índice que buscamos:
ALTER TABLE mv_t SET (fillfactor=90);
ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name) WITH (fillfactor=90);
Entonces su INSERT
podría verse así:
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 (...)
El fillfactor
solo es útil si su tabla recibe muchas actualizaciones.
Actualizar filas en la vista materializada en un TRIGGER AFTER INSERT OR UPDATE OF name, value OR DELETE
para mantenerlo actualizado. El costo de los objetos adicionales debe sopesarse frente a la ganancia. Depende en gran medida de su carga típica.