sql >> Database >  >> RDS >> PostgreSQL

Controlebeperkingen in PostgreSQL begrijpen

Het beheren van data is een grote uitdaging. Terwijl onze wereld draait, blijven gegevens wijdverbreid, overvloedig en intensief. Daarom moeten we maatregelen nemen om de toestroom op te vangen.

Elk stukje data valideren 'met de hand ' 24 uur per dag is gewoon onpraktisch. Wat een fantastische droom. Maar uiteindelijk is het gewoon dat. Een droom. Slechte gegevens zijn slechte gegevens. Het maakt niet uit hoe je het snijdt of in blokjes snijdt (bedoelde woordspeling). Het is vanaf het begin een probleem, dat tot nog meer problemen leidt.

Moderne databases verwerken veel van het zware werk voor ons. Veel bieden ingebouwde oplossingen om te helpen bij het beheren van dit specifieke gegevensgebied.

Een zekere manier om de gegevens die in de kolom van een tabel worden ingevoerd te beheren, is met een gegevenstype. Heb je een kolom nodig met decimale getallen, met een totaal aantal cijfers van 4, met 2 daarvan achter de komma?

Natuurlijk! Helemaal geen probleem.

NUMERIC(4,2), een haalbare optie, bewaakt die kolom als een waakhond. Kunnen karaktertekstwaarden daarin glippen? Geen sneeuwbalkans.

PostgreSQL biedt een groot aantal gegevenstypen. De kans is groot dat er al een bestaat om aan uw behoefte(n) te voldoen. Zo niet, dan kunt u uw eigen maken. (Zie:PostgreSQL CREATE TYPE)

Toch zijn datatypes alleen niet voldoende. U kunt er niet zeker van zijn dat de meest specifieke vereisten worden gedekt en voldoen aan een dergelijke brede structurering. Nalevingsregels en een soort 'standaard' zijn meestal vereist bij het ontwerpen van een schema.

Stel dat u in diezelfde kolom NUMERIC(4,2) alleen waarden wilt die groter zijn dan 25,25 maar kleiner dan 74,33? Als de waarde 88,22 wordt opgeslagen, is er geen fout aan het gegevenstype. Door in totaal 4 cijfers toe te staan, met maximaal 2 achter de komma, doet het zijn werk. Leg de schuld ergens anders.

Hoe winnen we op dit front als het gaat om het controleren van de gegevens die in onze database zijn toegestaan? Gegevensconsistentie heeft de hoogste prioriteit en is een integraal onderdeel van elke degelijke gegevensoplossing. Als u de verzamelde gegevens vanaf het begin van de oorspronkelijke bron beheert, is consistentie waarschijnlijk minder een probleem.

Maar een perfecte wereld bestaat (misschien) alleen in een van die vele fantasieromans die ik graag lees.

Helaas zijn onvolledige, inconsistente en 'vuile' gegevens maar al te veel voorkomende kenmerken en realiteiten die aanwezig zijn in een databasegericht veld.

Niet alles gaat echter verloren in kommer en kwel, want we hebben Check-beperkingen om deze problemen te verhelpen. Voor die specifieke regels moeten we, uit noodzaak, zorgen dat we alleen consistente gegevens verwerken en opslaan. Door deze specificaties in de database op te nemen, kunnen we de impact die inconsistente gegevens hebben op onze zakelijke doelen en toekomstige oplossingen minimaliseren.

Wat is een beperking? - Een definitie op hoog niveau

In deze context is een beperking een type regel of beperking die op een databasetabelkolom wordt geplaatst. Deze specificiteit vereist dat de gegevens die binnenkomen, moeten voldoen aan de gestelde eis(en) voordat ze worden opgeslagen. Genoemde vereiste(n) zijn meestal 'professioneel' bedacht (en zijn dat vaak) als bedrijfsregels . Dit komt neer op een validatie-booleaanse test voor de waarheid. Als de gegevens slagen (waar), worden deze opgeslagen. Zo niet, geen invoer (false).

Beschikbare beperkingen in PostgreSQL

Op het moment van schrijven somt de PostgreSQL-documentatie 6 categorieën van beperkingen op.

Dit zijn:

  • Beperkingen controleren
  • Niet-null-beperkingen
  • Unieke beperkingen
  • Primaire toetsen
  • Buitenlandse sleutels
  • Uitsluitingsbeperkingen

Beperkingen controleren

Een eenvoudig voorbeeld voor een INTEGER-kolom zou zijn om waarden groter dan bijvoorbeeld 100 niet toe te staan.

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

Zoals hierboven te zien is, mislukken pogingen om waarden in te voegen die de Check-beperking schenden.

Controlebeperkingen controleren niet alleen kolommen tijdens INSERT, zelfs UPDATE-instructies (en andere, bijv. \copy en COPY) moeten zich ook aan de beperkingen houden.

Stel dat de no_go-tabel deze waarde heeft:

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

Een UPDATE van de id-kolomwaarde naar een waarde die niet voldoet aan de Check-beperking mislukt ook:

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

Controlebeperkingen moeten 'logisch' zijn voor het gegevenstype van de doelkolom. Het is ongeldig om een ​​INTEGER-kolom te proberen en te beperken om het opslaan van tekstwaarden te verbieden, aangezien het gegevenstype dit zelf niet toestaat.

Zie dit voorbeeld waarin ik dat type Check-beperking probeer op te leggen tijdens het maken van tabellen:

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

Leven zonder controlebeperkingen

Een oud gezegde dat ik heb gehoord en dat me aanspreekt, is:"Je mist het water pas als de bron droogvalt . "

Zonder Check-beperkingen kunnen we zeker weten dat hun opmerkelijke voordeel het meest wordt gewaardeerd als je het zonder ze moet stellen.

Neem dit voorbeeld...

Om te beginnen hebben we deze tabel en gegevens die de materialen van het padoppervlak vertegenwoordigen:

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)

En deze tabel met namen van paden en zijn eigen 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)

We willen ervoor zorgen dat table-trails alleen surface_id's bevatten voor corresponderende waarden in de tabel surface_material.

Ja ja ik weet het. Je schreeuwt tegen me.

"Kan dit niet worden opgelost met een BUITENLANDSE SLEUTEL?!?"

Ja het kan. Maar ik gebruik het om een ​​generiek gebruik aan te tonen, samen met een valkuil om te weten (later vermeld in de post).

Zonder Check-beperkingen kunt u een TRIGGER gebruiken en voorkomen dat inconsistente waarden worden opgeslagen.

Hier is een grof (maar werkend) voorbeeld:

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

Pogingen om een ​​waarde in te voegen die geen corresponderende surface_id heeft in tabelsporen, mislukt:

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

De onderstaande zoekopdrachtresultaten bevestigen de 'aanstootgevende ' waarde is niet opgeslagen:

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)

Dat is zeker veel werk om ongewenste waarden te verbieden.

Laten we deze vereiste opnieuw implementeren met een Check-beperking.

Aangezien u geen subquery kunt gebruiken (dit is de reden waarom ik het bovenstaande voorbeeld heb gebruikt) in de feitelijke Check-beperkingsdefinitie, moeten de waarden hardgecodeerd zijn .

Voor een kleine tafel, of triviaal voorbeeld zoals hier gepresenteerd, is dit prima. In andere scenario's, waarbij u meer waarden opneemt, kunt u beter een alternatieve oplossing zoeken.

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

Hier heb ik de controlebeperking t_check genoemd in plaats van het systeem het een naam te laten geven.

(Opmerking:de eerder gedefinieerde check_me() FUNCTIE en bijbehorende TRIGGER werd verwijderd (niet weergegeven) voordat het onderstaande werd uitgevoerd INSERT.)

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

Kijk eens hoe makkelijk dat was! Geen TRIGGER en FUNCTIE nodig.

Controlebeperkingen maken dit soort werk gemakkelijk.

Wil je handig worden in de definitie van Check-beperking?

Dat kan.

Stel dat u een tabel nodig heeft met routes die wat vriendelijker zijn voor mensen met gevoelige enkels en knieën. Hier zijn geen harde oppervlakken gewenst.

U wilt er zeker van zijn dat elk wandelpad of -pad in tabel nice_trail een oppervlaktemateriaal heeft van 'grind' of 'vuil'.

Deze controlebeperking handelt die vereiste probleemloos af:

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

Dat werkt absoluut prima.

Maar hoe zit het met een FUNCTIE die beide ID's retourneert die nodig zijn om de Check te laten werken? Is een FUNCTIE toegestaan ​​in de definitie van de controlebeperking?

Ja, er kan er een worden opgenomen.

Hier is een werkend voorbeeld.

Eerst de hoofdtekst en definitie van de functie:

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;

Merk op dat in deze CREATE TABLE-instructie, ik de Check-beperking definieer in de 'tabel ' niveau, terwijl ik eerder alleen voorbeelden heb gegeven in de 'kolom ' niveau.

Controlebeperkingen gedefinieerd op tabelniveau zijn perfect geldig:

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

Deze inserts zijn allemaal goed:

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

Komt nu langs een INSERT voor een pad dat niet voldoet aan de beperking op kolom mat_surface_id:

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

Onze FUNCTION-aanroep in de Check-beperkingsdefinitie werkt zoals bedoeld, waardoor de ongewenste kolomwaarden worden beperkt.

Rook en spiegels?

Is alles wat het lijkt met Check-beperkingen? Allemaal zwart-wit? Geen gevel aan de voorkant?

Een voorbeeld dat het vermelden waard is.

We hebben een eenvoudige tabel waarin we willen dat de DEFAULT-waarde 10 is voor de alleenstaande INTEGER-kolom die aanwezig is:

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

Maar ik heb ook een Check-beperking toegevoegd die een waarde van 10 verbiedt, door te definiëren dat id niet gelijk kan zijn aan dat aantal.

Welke zal de dag winnen? De DEFAULT- of Check-beperking?

Het zal je misschien verbazen te weten welke het is.

Ik was.

Een willekeurige INSERT, werkt prima:

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

En een INSERT met de DEFAULT waarde:

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

Oeps...

Nogmaals, met een alternatieve syntaxis:

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

De Check-beperking wint het van de DEFAULT-waarde.

Oddball-voorbeeld

De beperking Check kan tijdens het maken vrijwel overal in de tabeldefinitie voorkomen. Zelfs op kolomniveau kan het worden ingesteld op een kolom die niet bij de controle betrokken is.

Hier is een voorbeeld om te illustreren:

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

Een INSERT om de beperking te testen:

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

Werkt zoals bedoeld.

VALIDATIE en NIET GELDIG

We hebben deze eenvoudige tabel en gegevens:

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

Stel dat we nu een controlebeperking moeten implementeren die alle waarden kleiner dan 50 verbiedt.

Stel je voor dat dit een grote tabel is die in productie is en we kunnen ons momenteel geen verworven slot veroorloven, als gevolg van een ALTER TABLE-statement. Maar je moet deze beperking op zijn plaats krijgen om vooruit te komen.

ALTER TABLE krijgt een slot (afhankelijk van elk ander subformulier). Zoals vermeld, is deze tafel in productie, dus we willen wachten tot we buiten de 'piekuren zijn '.

U kunt de optie GEEN GELDIG gebruiken bij het maken van de controlebeperking:

learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID; 
ALTER TABLE
Download de whitepaper vandaag PostgreSQL-beheer en -automatisering met ClusterControlLees wat u moet weten om PostgreSQL te implementeren, bewaken, beheren en schalenDownload de whitepaper

Voortzetting van de activiteiten, mocht een poging voor een INSERT of UPDATE die de controlebeperking schendt:

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

De kolomwaarde 'aanstootgevend' is niet toegestaan.

Tijdens downtime valideren we vervolgens de Check-beperking om deze toe te passen op (alle) reeds bestaande kolommen die mogelijk in strijd zijn met:

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

Het bericht is naar mijn mening nogal cryptisch. Maar het informeert ons wel dat er rijen zijn die niet aan de beperking voldoen.

Hier zijn enkele belangrijke punten die ik wilde opnemen uit de ALTER TABLE-documentatie (Verbiage rechtstreeks uit de documenten tussen aanhalingstekens):

  • Syntaxis:ADD table_constraint [ NOT VALID ] - Begeleidende beschrijving (gedeeltelijk) "Dit formulier voegt een nieuwe beperking toe aan een tabel met dezelfde syntaxis als CREATE TABLE, plus de optie NOT VALID, die momenteel alleen is toegestaan ​​voor refererende sleutels en CONTROLEER beperkingen. Als de beperking is gemarkeerd als NIET GELDIG, wordt de mogelijk langdurige eerste controle om te controleren of alle rijen in de tabel aan de beperking voldoen, overgeslagen."
  • Syntaxis:VALIDATE CONSTRAINT constraint_name - Begeleidende beschrijving (gedeeltelijk) "Dit formulier valideert een externe sleutel of controlebeperking die eerder is gemaakt als NIET GELDIG, door de tabel te scannen om er zeker van te zijn dat er geen rijen zijn waarvoor niet aan de beperking wordt voldaan. " "Validatie verkrijgt alleen een SHARE UPDATE EXCLUSIEF slot op de tafel die wordt gewijzigd."

Even terzijde, twee punten die het vermelden waard zijn, heb ik gaandeweg geleerd. Functies en subquery's die een set retourneren, zijn niet toegestaan ​​in de definities van controlebeperkingen. Ik weet zeker dat er anderen zijn en ik verwelkom alle feedback over hen in de reacties hieronder.

Controlebeperkingen zijn geweldig. Het is volkomen logisch om de 'ingebouwde' oplossingen van de PostgreSQL-database zelf te gebruiken om gegevensbeperkingen af ​​te dwingen. Tijd en moeite besteed aan het implementeren Controleer de beperkingen voor de benodigde kolom(men), dat weegt veel zwaarder dan het helemaal niet implementeren. Dus tijdwinst op de lange termijn. Hoe meer we op de database leunen om met dit soort vereisten om te gaan, hoe beter. Dit stelt ons in staat om ons te concentreren en onze middelen toe te passen op andere gebieden/aspecten van databasebeheer.

Bedankt voor het lezen.


  1. MySQL, MariaDB, Percona Server, MongoDB of PostgreSQL implementeren - gemakkelijk gemaakt met ClusterControl

  2. Een bestaande kolom wijzigen in een berekende kolom in SQL Server (T-SQL-voorbeeld)

  3. Oracle pivot-operator

  4. ORA-01219:database niet open:queries alleen toegestaan ​​op vaste tabellen/views