PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Ausschlussbeschränkung für eine Bitstring-Spalte mit bitweisem AND-Operator

Wie Ihre Bearbeitung verdeutlichte, haben Sie die Erweiterung btree_gist . Ohne sie würde das Beispiel bereits bei name WITH = fehlschlagen .

CREATE EXTENSION btree_gist;

Die von btree_gist installierten Operatorklassen decken viele Betreiber ab. Leider ist der & Betreiber gehört nicht dazu. Offensichtlich, weil es keinen boolean zurückgibt was von einem Betreiber erwartet wird, sich zu qualifizieren.

Alternative Lösung

Ich würde eine Kombination aus einem b-Baum mehrspaltigen Index verwenden (für Geschwindigkeit) und einen Trigger stattdessen. Sehen Sie sich diese Demo an, die auf PostgreSQL 9.1 getestet wurde :

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.

Sollte sehr gut funktionieren, sogar besser als die Ausschlussbeschränkung, da die Wartung eines B-Tree-Index billiger ist als ein GiST-Index. Und die Suche mit einfachen = Operatoren sollten schneller sein als hypothetische Suchen mit & Betreiber.

Diese Lösung ist nicht so sicher wie eine Ausschlussbeschränkung, da Trigger leichter umgangen werden können – beispielsweise bei einem nachfolgenden Trigger auf dasselbe Ereignis oder wenn der Trigger vorübergehend deaktiviert ist. Seien Sie bereit, zusätzliche Überprüfungen der gesamten Tabelle durchzuführen, wenn solche Bedingungen zutreffen.

Komplexerer Zustand

Der Beispiel-Trigger fängt nur die Umkehrung von value ab . Wie Sie in Ihrem Kommentar klargestellt haben, benötigen Sie stattdessen tatsächlich eine Bedingung wie diese:

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

Diese Bedingung ist etwas teurer, kann aber immer noch einen Index verwenden. Der mehrspaltige Index von oben würde funktionieren - falls Sie ihn sowieso brauchen. Oder, etwas effizienter, ein einfacher Index für Namen:

CREATE INDEX t_name_idx ON t (name);

Wie Sie kommentiert haben, kann es nur maximal 8 verschiedene Zeilen pro name geben , weniger in der Praxis. Das sollte also immer noch schnell gehen.

Ultimative INSERT-Leistung

Wenn INSERT Die Leistung ist von größter Bedeutung, insbesondere wenn viele versuchte INSERTs die Bedingung nicht erfüllen, können Sie mehr tun:Erstellen Sie eine materialisierte Ansicht, die value voraggregiert pro name :

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

name ist hier garantiert einzigartig. Ich würde einen PRIMARY KEY verwenden auf name um den gesuchten Index bereitzustellen:

ALTER TABLE mv_t SET (fillfactor=90);

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

Dann Ihr INSERT könnte so aussehen:

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 (...)

Der fillfactor ist nur nützlich, wenn Ihre Tabelle viele Aktualisierungen erhält.

Zeilen in der materialisierten Ansicht in einem TRIGGER AFTER INSERT OR UPDATE OF name, value OR DELETE aktualisieren um es aktuell zu halten. Die Kosten für die zusätzlichen Objekte müssen gegen den Gewinn abgewogen werden. Hängt weitgehend von Ihrer typischen Auslastung ab.