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

Check Constraints in PostgreSQL verstehen

Die Verwaltung von Daten ist eine große Herausforderung. Während sich unsere Welt dreht, sind Daten weiterhin weit verbreitet, reichlich vorhanden und intensiv. Daher müssen wir Maßnahmen ergreifen, um den Zustrom zu bewältigen.

Validierung jedes einzelnen Datenstücks 'von Hand ' Rund um die Uhr ist einfach unpraktisch. Was für ein fantastischer Traum. Aber schließlich ist es genau das. Ein Traum. Schlechte Daten sind schlechte Daten. Egal, wie Sie es schneiden oder würfeln (Wortspiel beabsichtigt). Es ist von Anfang an ein Problem, das zu noch mehr Problemen führt.

Moderne Datenbanken erledigen einen Großteil der schweren Arbeit für uns. Viele bieten integrierte Lösungen zur Unterstützung bei der Verwaltung dieses speziellen Datenbereichs.

Ein sicherer Weg, die in die Spalte einer Tabelle eingegebenen Daten zu kontrollieren, ist ein Datentyp. Benötigen Sie eine Spalte mit Dezimalzahlen mit einer Gesamtzahl von 4 Stellen, davon 2 nach dem Komma?

Sichere Sache! Überhaupt kein Problem.

NUMERIC(4,2), eine praktikable Option, bewacht diese Spalte wie ein Wachhund. Können sich dort Zeichentextwerte einschleichen? Keine Chance für einen Schneeball.

PostgreSQL bietet eine Vielzahl von Datentypen. Die Chancen stehen gut, dass bereits eine vorhanden ist, um Ihre Bedürfnisse zu befriedigen. Wenn nicht, können Sie Ihre eigenen erstellen. (Siehe:PostgreSQL CREATE TYPE)

Datentypen allein reichen jedoch nicht aus. Sie können nicht garantieren, dass die spezifischsten Anforderungen abgedeckt werden und einer so breiten Strukturierung entsprechen. Compliance-Regeln und eine Art „Standard“ sind normalerweise erforderlich, wenn ein Schema entworfen wird.

Angenommen, Sie möchten in derselben NUMERIC(4,2)-Spalte nur Werte größer als 25,25, aber kleiner als 74,33? Falls der Wert 88,22 gespeichert wird, ist der Datentyp nicht schuld. Indem es 4 Gesamtziffern zulässt, mit höchstens 2 nach dem Dezimalzeichen, erfüllt es seine Aufgabe. Schieben Sie die Schuld woanders hin.

Wie können wir an dieser Front gewinnen, wenn es darum geht, die in unserer Datenbank zulässigen Daten zu kontrollieren? Datenkonsistenz hat höchste Priorität und ist ein wesentlicher Bestandteil jeder soliden Datenlösung. Wenn Sie die gesammelten Daten (nicht) vom Beginn ihrer Entstehungsquelle an kontrolliert haben, wäre die Konsistenz wahrscheinlich weniger problematisch.

Aber eine heile Welt existiert (vielleicht) nur in einem dieser vielen Fantasy-Romane, die ich gerne lese.

Unglücklicherweise sind unvollständige, inkonsistente und „schmutzige“ Daten allzu häufige Merkmale und Realitäten in einem datenbankzentrierten Bereich.

Allerdings ist nicht alles in Untergangsstimmung verloren, denn wir haben Check-Einschränkungen, um diese Probleme zu mildern. Für diese spezifischen Regeln müssen wir notwendigerweise sicherstellen, dass wir nur konsistente Daten verarbeiten und speichern. Indem wir diese Spezifikationen in der Datenbank vorschreiben, können wir die Auswirkungen inkonsistenter Daten auf unsere Geschäftsziele und zukünftige Lösungen minimieren.

Was ist eine Einschränkung? - Eine High-Level-Definition

In diesem Zusammenhang ist eine Einschränkung eine Art Regel oder Einschränkung, die für eine Datenbanktabellenspalte gilt. Diese Besonderheit erfordert, dass die eingehenden Daten die festgelegten Anforderungen erfüllen müssen, bevor sie gespeichert werden. Diese Anforderung(en) werden in der Regel „professionell“ als Geschäftsregeln bezeichnet (und sind es oft). . Dies läuft auf einen booleschen Validierungstest für die Wahrheit hinaus. Wenn die Daten bestehen (true), werden sie gespeichert. Wenn nicht, kein Eintrag (false).

In PostgreSQL verfügbare Einschränkungen

Zum Zeitpunkt des Schreibens listet die PostgreSQL-Dokumentation 6 Kategorien von Beschränkungen auf.

Sie sind:

  • Einschränkungen prüfen
  • Nicht-Null-Einschränkungen
  • Eindeutige Einschränkungen
  • Primärschlüssel
  • Fremdschlüssel
  • Ausschlussbeschränkungen

Beschränkungen prüfen

Ein einfaches Beispiel für eine INTEGER-Spalte wäre, Werte größer als sagen wir 100 zu verbieten.

learning=> CREATE TABLE no_go(id INTEGER CHECK (id < 100));
CREATE TABLE
learning=> INSERT INTO no_go(id) VALUES(101);
ERROR: new row for relation "no_go" violates check constraint "no_go_id_check"
DETAIL: Failing row contains (101).

Wie oben zu sehen ist, schlägt der Versuch, Werte einzufügen, die die Check-Einschränkung verletzen, fehl.

Prüfbeschränkungen überwachen nicht nur Spalten während INSERT, auch UPDATE-Anweisungen (und andere, z. B. \copy und COPY) müssen sich ebenfalls an die Beschränkungen halten.

Angenommen, die no_go-Tabelle hat diesen Wert:

learning=> TABLE no_go;
id 
----
55
(1 row)

Ein UPDATE des ID-Spaltenwerts auf einen Wert, der nicht der Check-Einschränkung entspricht, schlägt ebenfalls fehl:

learning=> UPDATE no_go SET id = 155
learning-> WHERE id = 55;
ERROR: new row for relation "no_go" violates check constraint "no_go_id_check"
DETAIL: Failing row contains (155).

Check Constraints müssen für den Datentyp der Zielspalte „sinnvoll“ sein. Es ist ungültig, eine INTEGER-Spalte zu versuchen und einzuschränken, um das Speichern von Textwerten zu verbieten, da der Datentyp selbst dies nicht zulässt.

Sehen Sie sich dieses Beispiel an, in dem ich versuche, diese Art von Check-Einschränkung während der Tabellenerstellung aufzuerlegen:

learning=> CREATE TABLE num_try(id INTEGER CHECK(id IN ('Bubble', 'YoYo', 'Jack-In-The-Box')));
ERROR: invalid input syntax for integer: "Bubble"

Leben ohne Check Constraints

Ein altes Sprichwort, das mir zu Ohren kommt, lautet:„Du vermisst das Wasser nicht, bis der Brunnen trocken ist . "

Ohne Check-Einschränkungen können wir sicher sagen, dass ihr bemerkenswerter Nutzen am meisten geschätzt wird, wenn Sie ohne sie auskommen müssen.

Nehmen Sie dieses Beispiel…

Zu Beginn haben wir diese Tabelle und Daten, die Oberflächenmaterialien von Wanderwegen darstellen:

learning=> SELECT * FROM surface_material;
surface_id | material 
------------+--------------
101 | Gravel
202 | Grass
303 | Dirt
404 | Turf
505 | Concrete
606 | Asphalt
707 | Clay
808 | Polyurethane
(8 rows)

Und diese Tabelle mit Pfadnamen und eigener Surface_id:

learning=> SELECT * FROM trails;
id | name | surface_id 
----+-----------------+------------
1 | Dusty Storm | 303
2 | Runners Trip | 808
3 | Pea Gravel Pass | 101
4 | Back 40 Loop | 404
(4 rows)

Wir möchten sicherstellen, dass die Tabelle Trails nur Oberflächen-IDs für entsprechende Werte in der Tabelle Oberflächenmaterial enthält.

Ja Ja ich weiss. Du schreist mich an.

"Kann das nicht mit einem erledigt werden FREMDSCHLÜSSEL?!?"

Ja, kann es. Aber ich verwende es, um eine generische Verwendung zu demonstrieren, zusammen mit einem Fallstrick, den Sie kennen sollten (später in diesem Beitrag erwähnt).

Ohne Check Constraints können Sie auf einen TRIGGER zurückgreifen und verhindern, dass inkonsistente Werte gespeichert werden.

Hier ist ein grobes (aber funktionierendes) Beispiel:

CREATE OR REPLACE FUNCTION check_me()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.surface_id NOT IN (SELECT surface_id FROM surface_material)
THEN Raise Exception '% is not allowed for surface id', NEW.surface_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE PLpgSQL;
CREATE TRIGGER check_me BEFORE INSERT OR UPDATE ON trails
FOR EACH ROW EXECUTE PROCEDURE check_me();

Versuche, einen Wert einzufügen, der keine entsprechende Oberflächen-ID in den Tabellenspuren hat, schlagen fehl:

learning=> INSERT INTO trails(name, surface_id)
learning-> VALUES ('Tennis Walker', 110);
ERROR: 110 is not allowed for surface id
CONTEXT: PL/pgSQL function check_me() line 4 at RAISE

Die nachstehenden Abfrageergebnisse bestätigen die 'Beleidigung ' Wert wurde nicht gespeichert:

learning=> SELECT * FROM trails;
id | name | surface_id 
----+-----------------+------------
1 | Dusty Storm | 303
2 | Runners Trip | 808
3 | Pea Gravel Pass | 101
4 | Back 40 Loop | 404
(4 rows)

Das ist sicher eine Menge Arbeit, um unerwünschte Werte zu verhindern.

Lassen Sie uns diese Anforderung mit einer Check-Einschränkung erneut implementieren.

Da Sie keine Unterabfrage verwenden können (aus diesem Grund habe ich das obige Beispiel verwendet) in der eigentlichen Check-Constraint-Definition, müssen die Werte fest codiert werden .

Für eine kleine Tabelle oder ein triviales Beispiel wie das hier vorgestellte ist das in Ordnung. In anderen Szenarien, die mehr Werte beinhalten, sind Sie möglicherweise besser bedient, wenn Sie nach einer alternativen Lösung suchen.

learning=> ALTER TABLE trails ADD CONSTRAINT t_check CHECK (surface_id IN (101, 202, 303, 404, 505, 606, 707, 808));
ALTER TABLE

Hier habe ich die Check-Einschränkung t_check genannt, anstatt sie vom System benennen zu lassen.

(Hinweis:Die zuvor definierte check_me() FUNCTION und begleitende TRIGGER wurden gelöscht (nicht gezeigt), bevor das Folgende ausgeführt wurde EINFÜGEN.)

learning=> INSERT INTO trails(name, surface_id)
VALUES('Tennis Walker', 110);
ERROR: new row for relation "trails" violates check constraint "t_check"
DETAIL: Failing row contains (7, Tennis Walker, 110).

Würden Sie sich ansehen, wie einfach das war! Kein TRIGGER und keine FUNKTION erforderlich.

Check Constraints machen diese Art von Arbeit kinderleicht.

Möchten Sie in der Check-Einschränkungsdefinition schlau werden?

Sie können.

Angenommen, Sie benötigen eine Tabelle mit Wanderwegen, die für Menschen mit empfindlichen Knöcheln und Knien etwas sanfter sind. Hier sind keine harten Oberflächen erwünscht.

Sie möchten sicherstellen, dass alle in der Tabelle nice_trail aufgelisteten Wanderwege oder Tracks ein Oberflächenmaterial von entweder „Gravel“ oder „Dirt“ haben.

Diese Check-Einschränkung bewältigt diese Anforderung problemlos:

learning=> CREATE TABLE nice_trail(id SERIAL PRIMARY KEY,
learning(> name TEXT, mat_surface_id INTEGER CONSTRAINT better_surface CHECK(id IN (101, 303))); 
CREATE TABLE

Das funktioniert absolut einwandfrei.

Aber wie wäre es mit einer FUNCTION, die beide IDs zurückgibt, die erforderlich sind, damit der Check funktioniert? Ist eine FUNCTION in der Check Constraint-Definition erlaubt?

Ja, man kann eingearbeitet werden.

Hier ist ein funktionierendes Beispiel.

Zuerst der Funktionsrumpf und die Definition:

CREATE OR REPLACE FUNCTION easy_hike(id INTEGER)
RETURNS BOOLEAN AS
$$
BEGIN
IF id IN (SELECT surface_id FROM surface_material WHERE material IN ('Gravel', 'Dirt'))
THEN RETURN true;
ELSE RETURN false;
END IF;
END;
$$ LANGUAGE PLpgSQL;

Beachten Sie, dass ich in dieser CREATE TABLE-Anweisung die Check-Einschränkung in der 'Tabelle definiere '-Ebene, während ich zuvor nur Beispiele in der 'Spalte bereitgestellt habe ' Ebene.

Auf Tabellenebene definierte Check Constraints sind vollkommen gültig:

learning=> CREATE TABLE nice_trail(nt_id SERIAL PRIMARY KEY,
learning(> name TEXT, mat_surface_id INTEGER,
learning(> CONSTRAINT better_surface_check CHECK(easy_hike(mat_surface_id)));
CREATE TABLE

Diese Einfügungen sind alle gut:

learning=> INSERT INTO nice_trail(name, mat_surface_id)
learning-> VALUES ('Smooth Rock Loop', 101), ('High Water Bluff', 303);
INSERT 0 2

Jetzt kommt ein INSERT für einen Pfad, der die Beschränkung auf Spalte mat_surface_id nicht erfüllt:

learning=> INSERT INTO nice_trail(name, mat_surface_id)
learning-> VALUES('South Branch Fork', 404);
ERROR: new row for relation "nice_trail" violates check constraint "better_surface_check"
DETAIL: Failing row contains (3, South Branch Fork, 404).

Unser FUNCTION-Aufruf in der Check-Constraint-Definition funktioniert wie vorgesehen und schränkt die unerwünschten Spaltenwerte ein.

Rauch und Spiegel?

Ist mit Check Constraints alles so, wie es scheint? Alles schwarz und weiß? Keine Fassade vorn?

Ein erwähnenswertes Beispiel.

Wir haben eine einfache Tabelle, in der wir wollen, dass der DEFAULT-Wert 10 für die einzige vorhandene INTEGER-Spalte ist:

learning=> CREATE TABLE surprise(id INTEGER DEFAULT 10, CHECK (id <> 10));
CREATE TABLE

Aber ich habe auch eine Check-Einschränkung eingefügt, die einen Wert von 10 verbietet, indem ich definiert habe, dass id nicht gleich dieser Zahl sein darf.

Welcher wird den Tag gewinnen? Die Einschränkung DEFAULT oder Check?

Sie werden überrascht sein zu wissen, was es ist.

Ich war.

Ein willkürlicher INSERT, der gut funktioniert:

learning=> INSERT INTO surprise(id) VALUES(17);
INSERT 0 1
learning=> SELECT * FROM surprise;
id 
----
17
(1 row)

Und ein INSERT mit dem DEFAULT-Wert:

learning=> INSERT INTO surprise(id) VALUES(DEFAULT);
ERROR: new row for relation "surprise" violates check constraint "surpise_id_check"
DETAIL: Failing row contains (10).

Hoppla...

Wieder mit einer alternativen Syntax:

learning=> INSERT INTO surprise DEFAULT VALUES;
ERROR: new row for relation "surprise" violates check constraint "surpise_id_check"
DETAIL: Failing row contains (10).

Die Check-Einschränkung gewinnt gegenüber dem DEFAULT-Wert.

Ungewöhnliches Beispiel

Die Check-Einschränkung kann während der Erstellung praktisch überall in der Tabellendefinition erscheinen. Auch auf Spaltenebene kann es auf eine Spalte gesetzt werden, die überhaupt nicht an der Prüfung beteiligt ist.

Hier ist ein Beispiel zur Veranschaulichung:

learning=> CREATE TABLE mystery(id_1 INTEGER CHECK(id_2 > id_3),
learning(> id_2 INTEGER, id_3 INTEGER);
CREATE TABLE

Ein INSERT zum Testen der Einschränkung:

learning=> INSERT INTO mystery(id_1, id_2, id_3) VALUES (1, 2, 3);
ERROR: new row for relation "mystery" violates check constraint "mystery_check"
DETAIL: Failing row contains (1, 2, 3).

Funktioniert wie vorgesehen.

VALIDIERUNG und NICHT GÜLTIG

Wir haben diese einfache Tabelle und Daten:

learning=> CREATE TABLE v_check(id INTEGER);
CREATE TABLE
learning=> INSERT INTO v_check SELECT * FROM generate_series(1, 425);
INSERT 0 425

Angenommen, wir müssen jetzt eine Check-Einschränkung implementieren, die alle Werte unter 50 verbietet.

Stellen Sie sich vor, dies ist eine große Tabelle in der Produktion, und wir können uns im Moment keine erworbene Sperre leisten, die aus einer ALTER TABLE-Anweisung resultiert. Aber Sie müssen diese Einschränkung in Kraft setzen, um voranzukommen.

ALTER TABLE erwirbt eine Sperre (abhängig von den verschiedenen Unterformularen). Wie bereits erwähnt, befindet sich dieser Tisch in der Produktion, daher möchten wir warten, bis wir außerhalb der 'Spitzenzeiten sind '.

Sie können die Option NO VALID verwenden, wenn Sie die Check-Einschränkung erstellen:

learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID; 
ALTER TABLE
Laden Sie noch heute das Whitepaper PostgreSQL-Verwaltung und -Automatisierung mit ClusterControl herunterErfahren Sie, was Sie wissen müssen, um PostgreSQL bereitzustellen, zu überwachen, zu verwalten und zu skalierenLaden Sie das Whitepaper herunter

Fortsetzung des Vorgangs, falls ein Versuch für ein INSERT oder UPDATE, das die Check-Einschränkung verletzt, auftreten sollte:

learning=> INSERT INTO v_check(id) VALUES(22);
ERROR: new row for relation "v_check" violates check constraint "fifty_chk"
DETAIL: Failing row contains (22).

Der Spaltenwert „anstößig“ ist verboten.

Dann validieren wir während der Ausfallzeit die Check-Einschränkung, um sie auf (jede) bereits vorhandene Spalten anzuwenden, die möglicherweise einen Verstoß darstellen:

learning=> ALTER TABLE v_check VALIDATE CONSTRAINT fifty_chk;
ERROR: check constraint "fifty_chk" is violated by some row

Die Nachricht ist meiner Meinung nach ziemlich kryptisch. Aber es informiert uns, dass es Zeilen gibt, die nicht mit der Einschränkung übereinstimmen.

Hier sind einige wichtige Punkte, die ich aus der ALTER TABLE-Dokumentation aufnehmen wollte (Wort direkt aus der Dokumentation in Anführungszeichen):

  • Syntax:ADD table_constraint [ NOT VALID ] - Begleitende Beschreibung (teilweise) "Dieses Formular fügt einer Tabelle eine neue Einschränkung hinzu, die dieselbe Syntax wie CREATE TABLE verwendet, plus der Option NOT VALID, die derzeit nur für Fremdschlüssel und zulässig ist Einschränkungen prüfen. Wenn die Einschränkung als NICHT GÜLTIG gekennzeichnet ist, wird die möglicherweise langwierige anfängliche Prüfung übersprungen, um sicherzustellen, dass alle Zeilen in der Tabelle die Einschränkung erfüllen."
  • Syntax:VALIDATE CONSTRAINT constraint_name - Begleitende Beschreibung (teilweise) "Dieses Formular validiert einen Fremdschlüssel oder eine Check-Einschränkung, die zuvor als NICHT GÜLTIG erstellt wurde, indem die Tabelle gescannt wird, um sicherzustellen, dass es keine Zeilen gibt, für die die Einschränkung nicht erfüllt ist. " "Die Validierung erwirbt nur eine SHARE UPDATE EXCLUSIVE-Sperre für die Tabelle, die geändert wird."

Nebenbei noch zwei erwähnenswerte Punkte, die ich auf dem Weg gelernt habe. Set-zurückgebende Funktionen und Unterabfragen sind in Check-Constraint-Definitionen nicht zulässig. Ich bin mir sicher, dass es noch andere gibt, und ich freue mich über jedes Feedback dazu in den Kommentaren unten.

Check Constraints sind genial. Die Verwendung der „eingebauten“ Lösungen, die von der PostgreSQL-Datenbank selbst bereitgestellt werden, um Datenbeschränkungen durchzusetzen, ist absolut sinnvoll. Der Zeit- und Arbeitsaufwand für die Implementierung von Check-Constraints für notwendige Spalte(n) überwiegt bei weitem, wenn überhaupt keine implementiert werden. So sparen Sie langfristig Zeit. Je mehr wir uns auf die Datenbank stützen, um diese Art von Anforderungen zu bewältigen, desto besser. Dadurch können wir unsere Ressourcen auf andere Bereiche/Aspekte der Datenbankverwaltung konzentrieren und anwenden.

Danke fürs Lesen.