sql >> Databasteknik >  >> RDS >> PostgreSQL

Exkluderingsbegränsning på en bitsträngskolumn med bitvis AND-operator

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.

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ÄRNYCKELnamn 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.




  1. OracleCommand med OracleDependency väntar för evigt

  2. Rå relevansfråga i Laravel. Hur ska man hantera det?

  3. Hur kan jag definiera denna begränsning?

  4. Hur lägger man till parametervärden i pgadmin sql-frågan?