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.
-
Die Erweiterung
btree_gist
ist nicht in diesem Szenario erforderlich. -
Ich habe den Trigger auf INSERT oder UPDATE relevanter Spalten beschränkt für Effizienz.
-
Eine Check-Einschränkung würde nicht funktionieren. Ich zitiere das Handbuch zu
CREATE TABLE
:Fette Hervorhebung von mir:
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.