Som din redigering förtydligades installerade du tillägget btree_gist
. Utan det skulle exemplet redan misslyckas vid name WITH =
.
CREATE EXTENSION btree_gist;
Operatörsklasserna installerade av btree_gist
täcka många operatörer. Tyvärr, &
operatör är inte bland dem. Uppenbarligen för att den inte returnerar en boolean
vilket skulle förväntas av en operatör att kvalificera sig.
Alternativ lösning
Jag skulle använda en kombination av ett b-träd flerkolumnindex (för hastighet) och en trigger istället. Tänk på den här demon, testad på 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.
-
Tillägget
btree_gist
är inte krävs i detta scenario. -
Jag begränsade utlösaren till att INFOGA eller UPPDATERA relevanta kolumner för effektivitet.
-
En kontrollbegränsning skulle inte fungera. Jag citerar manualen om
SKAPA TABELL
:Djärv betoning min:
Bör prestera mycket bra, faktiskt bättre än uteslutningsbegränsningen, eftersom underhåll av ett b-tree-index är billigare än ett GiST-index. Och uppslagningen med grundläggande =
Operatörer bör vara snabbare än hypotetiska uppslagningar med &
operatör.
Denna lösning är inte lika säker som en uteslutningsrestriktion, eftersom utlösare lättare kan kringgås - till exempel i en efterföljande utlösare på samma händelse, eller om utlösaren är tillfälligt inaktiverad. Var beredd att köra extra kontroller på hela bordet om sådana villkor gäller.
Mer komplext tillstånd
Exemplet triggern fångar bara inversionen av värde
. Som du förtydligade i din kommentar behöver du faktiskt ett tillstånd som detta istället:
IF EXISTS (
SELECT 1 FROM t
WHERE name = NEW.name
AND value & NEW.value <> B'00000000'::bit(8)
) THEN
Detta villkor är något dyrare, men kan fortfarande använda ett index. Flerkolumnsindexet från ovan skulle fungera - om du ändå har behov av det. Eller, lite mer effektivt, ett enkelt namnindex:
CREATE INDEX t_name_idx ON t (name);
Som du kommenterade kan det bara finnas maximalt 8 distinkta rader per namn
färre i praktiken. Så det här borde fortfarande gå snabbt.
Ultimat INSERT-prestanda
Om INSERT
prestanda är av största vikt, speciellt om många INSERT-försök misslyckas med villkoret, kan du göra mer:skapa en materialiserad vy som föraggregerat värde
per namn
:
CREATE TABLE mv_t AS
SELECT name, bit_or(value) AS value
FROM t
GROUP BY 1
ORDER BY 1;
namn
är garanterat unik här. Jag skulle använda en PRIMÄRNYCKEL
på namn
för att tillhandahålla indexet vi är ute efter:
ALTER TABLE mv_t SET (fillfactor=90);
ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name) WITH (fillfactor=90);
Sedan din INSERT
kan se ut så här:
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 (...)
fillfactor
är bara användbart om din tabell får många uppdateringar.
Uppdatera rader i den materialiserade vyn i en TRIGGER EFTER INFOGA ELLER UPPDATERING AV namn, värde ELLER DELETE
för att hålla den aktuell. Kostnaden för tilläggsobjekten måste vägas mot vinsten. Beror till stor del på din typiska belastning.