Jak vaše úprava objasnila, nainstalovali jste rozšíření btree_gist
. Bez něj by příklad již selhal na name WITH =
.
CREATE EXTENSION btree_gist;
Třídy operátorů nainstalované btree_gist
pokrývat mnoho operátorů. Bohužel &
operátor mezi nimi není. Je zřejmé, že nevrací boolean
což se očekává od operátora, aby se kvalifikoval.
Alternativní řešení
Použil bych kombinaci vícesloupcového indexub-stromu (pro rychlost) a spouštěč namísto. Zvažte toto demo, testované na 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.
-
Rozšíření
btree_gist
není vyžadováno v tomto scénáři. -
Omezil jsem spouštěč na INSERT nebo UPDATE relevantních sloupců pro efektivitu.
-
Kontrolní omezení by nefungovalo. Cituji příručku k
CREATE TABLE
:Tučné zdůraznění moje:
Mělo by fungovat velmi dobře, ve skutečnosti lépe než omezení vyloučení, protože údržba indexu b-stromu je levnější než index GiST. A vyhledávání pomocí základního =
operátory by měly být rychlejší než hypotetické vyhledávání pomocí &
operátor.
Toto řešení není tak bezpečné jako omezení vyloučení, protože spouštěče lze snadněji obejít – například při následné spouštěcí události stejné události nebo pokud je spouštěč dočasně zakázán. Buďte připraveni spustit dodatečné kontroly celého stolu, pokud takové podmínky platí.
Složitější podmínka
Příklad spouštěče zachytí pouze inverzi value
. Jak jste ve svém komentáři objasnili, ve skutečnosti potřebujete podmínku jako je tato:
IF EXISTS (
SELECT 1 FROM t
WHERE name = NEW.name
AND value & NEW.value <> B'00000000'::bit(8)
) THEN
Tato podmínka je o něco dražší, ale stále lze použít index. Fungoval by vícesloupcový index shora - pokud jej přesto potřebujete. Nebo, o něco efektivnější, jednoduchý index na jméno:
CREATE INDEX t_name_idx ON t (name);
Jak jste uvedli, na name
může být maximálně 8 různých řádků , v praxi méně. Takže by to mělo být stále rychlé.
Maximální výkon INSERT
Pokud INSERT
výkon je prvořadý, zvláště pokud mnoho pokusů o INSERT nesplňuje podmínku, můžete udělat více:vytvořit materializovaný pohled s předem agregovanou hodnotou
podle jména
:
CREATE TABLE mv_t AS
SELECT name, bit_or(value) AS value
FROM t
GROUP BY 1
ORDER BY 1;
jméno
je zde zaručeně unikátní. Použil bych PRIMARY KEY
na jméno
abychom poskytli index, o který usilujeme:
ALTER TABLE mv_t SET (fillfactor=90);
ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name) WITH (fillfactor=90);
Poté váš INSERT
může vypadat takto:
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 (...)
faktor plnění
je užitečné pouze v případě, že váš stůl dostává mnoho aktualizací.
Aktualizujte řádky v materializovaném zobrazení v SPUŠTĚNÍ PO VLOŽENÍ NEBO AKTUALIZACI názvu, hodnoty NEBO DELETE
aby to bylo aktuální. Náklady na dodatečné objekty je třeba porovnat se ziskem. Do značné míry závisí na vaší typické zátěži.