sql >> Database teknologi >  >> RDS >> PostgreSQL

Ekskluderingsbegrænsning på en bitstrengskolonne med bitvise AND-operator

Som din redigering blev tydeliggjort, installerede du udvidelsen btree_gist . Uden det ville eksemplet allerede mislykkes ved name WITH = .

CREATE EXTENSION btree_gist;

Operatørklasserne installeret af btree_gist dække mange operatører. Desværre er & operatør er ikke blandt dem. Selvfølgelig fordi det ikke returnerer en boolean hvilket ville forventes af en operatør at kvalificere sig.

Alternativ løsning

Jeg ville bruge en kombination af et b-træ flerkolonneindeks (for hastighed) og en trigger i stedet. Overvej denne demo, testet 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 fungere meget godt, faktisk bedre end udelukkelsesbegrænsningen, fordi vedligeholdelse af et b-træ-indeks er billigere end et GiST-indeks. Og opslag med grundlæggende = operatører skal være hurtigere end hypotetiske opslag med & operatør.

Denne løsning er ikke så sikker som en udelukkelsesbegrænsning, fordi triggere lettere kan omgås - for eksempel i en efterfølgende trigger på samme begivenhed, eller hvis triggeren er deaktiveret midlertidigt. Vær forberedt på at køre ekstra kontrol på hele bordet, hvis sådanne betingelser gør sig gældende.

Mere kompleks tilstand

Eksempeludløseren fanger kun inversionen af ​​værdi . Som du præciserede i din kommentar, har du faktisk brug for en tilstand som denne i stedet:

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

Denne betingelse er lidt dyrere, men kan stadig bruge et indeks. Multi-kolonne indekset fra oven ville fungere - hvis du alligevel har brug for det. Eller, lidt mere effektivt, et simpelt navneindeks:

CREATE INDEX t_name_idx ON t (name);

Som du kommenterede, kan der maksimalt være 8 forskellige rækker pr. navn , færre i praksis. Så det burde stadig være hurtigt.

Ultimativ INSERT ydeevne

Hvis INSERT ydeevne er altafgørende, især hvis mange INSERT-forsøg mislykkes med betingelsen, kan du gøre mere:skabe en materialiseret visning med præ-aggregeret værdi efter navn :

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

navn er garanteret unik her. Jeg ville bruge en PRIMÆR NØGLEnavn for at give det indeks, vi leder efter:

ALTER TABLE mv_t SET (fillfactor=90);

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

Derefter din INSERT kunne se sådan ud:

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 er kun nyttig, hvis din tabel får mange opdateringer.

Opdater rækker i den materialiserede visning i en TRIGGER EFTER INDSÆT ELLER OPDATERING AF navn, værdi ELLER SLETT at holde det aktuelt. Omkostningerne ved de ekstra genstande skal afvejes mod gevinsten. Afhænger i høj grad af din typiske belastning.




  1. Indsættelse og transformation af data fra SQL-tabel

  2. Kasusudtryk fungerer ikke korrekt i sql-forespørgsel

  3. PostgreSQL:Hvorfor bruger denne forespørgsel ikke mit indeks?

  4. Hvordan erstatter man kommaseparerede afdelings-id'er med henholdsvis deres navn?