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

Forståelse af kontrolbegrænsninger i PostgreSQL

Håndtering af data er en stor udfordring. Som vores verden vender, fortsætter data med at være udbredt, rigelige og intensive. Derfor skal vi tage skridt til at håndtere tilstrømningen.

Validering af hvert enkelt stykke data 'med hånden ' døgnet rundt er simpelthen upraktisk. Hvilken fantastisk drøm. Men trods alt er det bare det. En drøm. Dårlige data er dårlige data. Uanset hvordan du skærer den i skiver eller terninger (pun intended). Det er et problem fra starten, hvilket fører til endnu flere problemer.

Moderne databaser håndterer meget af de tunge løft for os. Mange leverer indbyggede løsninger til at hjælpe med at administrere dette særlige dataområde.

En sikker måde at kontrollere de data, der indtastes i en tabels kolonne, er med en datatype. Har du brug for en kolonne med decimaltal, med et samlet cifferantal på 4, med 2 af dem efter decimalen?

Det er sikkert! Intet problem overhovedet.

NUMERIC(4,2), en brugbar mulighed, vogter den kolonne som en vagthund. Kan tegntekstværdier glide ind der? Ikke en snebolds chance.

PostgreSQL tilbyder et væld af datatyper. Chancerne er, at der allerede findes en for at tilfredsstille dine behov. Hvis ikke, kan du oprette din egen. (Se:PostgreSQL OPRET TYPE)

Alligevel er datatyper alene ikke nok. Du kan ikke garantere, at de mest specifikke krav er dækket og er i overensstemmelse med en sådan bred strukturering. Overholdelsesregler og en slags 'standard' er typisk påkrævet, når man designer et skema.

Antag, at du i den samme NUMERIC(4,2) kolonne kun vil have værdier større end 25,25, men mindre end 74,33? I tilfælde af, at værdien 88.22 er gemt, er datatypen ikke fejlagtig. Ved at tillade 4 samlede cifre, med højst 2 efter decimalen, gør den sit arbejde. Læg skylden et andet sted.

Hvordan vinder vi på denne front, når det kommer til at kontrollere de tilladte data i vores database? Datakonsistens er af højeste prioritet og er integreret i enhver sund dataløsning. På den (fra) chance for, at du kontrollerede de indsamlede data fra begyndelsen af ​​dens oprindelseskilde, ville konsistens sandsynligvis være mindre af et problem.

Men en perfekt verden eksisterer (måske) kun i en af ​​de mange fantasy-romaner, jeg elsker at læse.

Desværre er ufuldstændige, inkonsekvente og 'beskidte' data alt for almindelige karakteristika og realiteter, der er til stede i et database-centreret felt.

Men ikke alt er tabt i undergang og dysterhed, for vi har Check-begrænsninger for at afbøde disse problemer. For disse specifikke regler skal vi af nødvendighed indføre, der sikrer, at vi kun håndterer og opbevarer konsistente data. Ved at pålægge disse specifikationer i databasen kan vi minimere den indvirkning, som inkonsistente data har på vores forretningsmål og -løsninger i fremtiden.

Hvad er en begrænsning? - En definition på højt niveau

I denne sammenhæng er en begrænsning en type regel eller begrænsning placeret på en databasetabelkolonne. Denne specificitet kræver, at de data, der kommer ind, skal overholde de(t) fastsatte krav, før de gemmes. Disse krav har en tendens til at være "professionelt" opfundet (og er det ofte) som forretningsregler . Dette bunder i en boolsk valideringstest for sandhed. Hvis dataene passerer (sandt), bliver de gemt. Hvis ikke, ingen indtastning (falsk).

Tilgængelige begrænsninger i PostgreSQL

I skrivende stund oplister PostgreSQL-dokumentationen 6 kategorier af begrænsninger.

De er:

  • Tjek begrænsninger
  • Ikke-nul-begrænsninger
  • Unikke begrænsninger
  • Primære nøgler
  • Udenlandske nøgler
  • Ekskluderingsbegrænsninger

Tjek begrænsninger

Et simpelt eksempel på en HELTAL-kolonne ville være at ikke tillade værdier større end f.eks. 100.

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

Som det fremgår af ovenstående, mislykkes forsøg på at INDSÆTTE værdier, der overtræder kontrolbetingelsen.

Tjek begrænsninger overvåger ikke kun kolonner under INSERT, selv UPDATE-sætninger (og andre, f.eks. \copy og COPY) skal også overholde begrænsningerne.

Antag, at no_go-tabellen har denne værdi:

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

En OPDATERING på id-kolonnens værdi til en, der ikke overholder Check-begrænsningen, mislykkes også:

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

Tjek begrænsninger skal 'give mening' for målkolonnens datatype. Det er ugyldigt at forsøge og begrænse en INTEGER-kolonne for at forbyde lagring af tekstværdier, da datatypen i sig selv ikke tillader det.

Se dette eksempel, hvor jeg forsøger at pålægge den type Check-begrænsning under oprettelse af tabel:

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

Liv uden check-begrænsninger

Et gammelt ordsprog, som jeg har hørt, er genklang hos mig, er:"Du går ikke glip af vandet, før brønden løber tør . "

Uden Check-begrænsninger kan vi helt sikkert forholde os, for deres bemærkelsesværdige fordel er mest værdsat, når du skal klare dig uden dem.

Tag dette eksempel...

Til at starte med har vi denne tabel og data, der repræsenterer sporoverfladematerialer:

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)

Og denne tabel med spornavne og sin egen overflade_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)

Vi ønsker at sikre, at tabelspor kun indeholder overflade_id'er for tilsvarende værdier over i tabel overflademateriale.

Ja ja, jeg ved det. Du skriger af mig.

"Kan dette ikke klares med en UDENLANDSKE NØGLE?!?"

Ja den kan. Men jeg bruger det til at demonstrere en generisk brug sammen med en faldgrube at kende (nævnt senere i indlægget).

Uden Check-begrænsninger kan du ty til en TRIGGER og forhindre, at inkonsistente værdier gemmes.

Her er et groft (men fungerende) eksempel:

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

Forsøg på at INDSÆTTE en værdi, der ikke har et tilsvarende overflade_id i tabelspor, mislykkes:

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

Forespørgselsresultaterne nedenfor bekræfter 'fornærmende ' værdi blev ikke gemt:

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)

Det er helt sikkert en masse arbejde for at forbyde uønskede værdier.

Lad os genimplementere dette krav med en Check-begrænsning.

Da du ikke kan bruge en underforespørgsel (her er grunden til, at jeg brugte eksemplet ovenfor) i den faktiske Check constraint definition, skal værdierne være hard-coded .

For et lille bord eller et trivielt eksempel som præsenteret her, er dette fint. I andre scenarier, med flere værdier, kan du være bedre tjent med at søge en alternativ løsning.

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

Her har jeg navngivet Check-begrænsningen t_check versus at lade systemet navngive den.

(Bemærk:Den tidligere definerede check_me() FUNKTION og medfølgende TRIGGER blev droppet (ikke vist), før nedenstående blev kørt INDSÆT.)

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

Vil du se hvor nemt det var! Ingen TRIGGER og FUNKTION påkrævet.

Tjek begrænsninger gør denne type arbejde nem.

Vil du blive smart i definitionen af ​​Check-begrænsning?

Det kan du.

Antag, at du har brug for en tabel med stier, der er lidt venligere for dem med følsomme ankler og knæ. Her ønskes ingen hårde overflader.

Du vil sikre dig, at enhver vandresti eller bane, der er opført i tabellen nice_trail, har et overflademateriale af enten 'Grus' eller 'Snavs'.

Denne Check-begrænsning håndterer dette krav uden problemer:

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

Det fungerer helt fint.

Men hvad med en FUNKTION, der returnerer begge id'er, der kræves for at få checken til at fungere? Er en FUNKTION tilladt i Check-begrænsningsdefinitionen?

Ja, man kan blive inkorporeret.

Her er et fungerende eksempel.

Først op, funktionsteksten og definitionen:

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;

Bemærk i denne CREATE TABLE-sætning definerer jeg Check-begrænsningen ved 'tabellen ' niveau, hvorimod jeg tidligere kun har givet eksempler på 'kolonnen ' niveau.

Check-begrænsninger defineret på tabelniveau er helt gyldige:

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

Disse indsatser er alle gode:

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

Nu følger en INSERT for et spor, der ikke opfylder begrænsningen på kolonne 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).

Vores FUNCTION-kald i Check-begrænsningsdefinitionen fungerer som designet og begrænser de uønskede kolonneværdier.

Røg og spejle?

Er alt, som det ser ud med Check-begrænsninger? Helt sort og hvidt? Ingen facade foran?

Et eksempel der er værd at bemærke.

Vi har en simpel tabel, hvori vi ønsker, at DEFAULT-værdien skal være 10 for den tilstedeværende eneste HELTAL-kolonne:

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

Men jeg har også inkluderet en check-begrænsning, der forbyder en værdi på 10, ved at definere id kan ikke være lig med det tal.

Hvem vinder dagen? STANDARD eller tjek begrænsningen?

Du bliver måske overrasket over at vide, hvilken det er.

Jeg var.

En vilkårlig INSERT, der fungerer fint:

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

Og en INSERT med DEFAULT-værdien:

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

Ups...

Igen, med en alternativ syntaks:

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

Check-begrænsningen vinder over DEFAULT-værdien.

Ulige eksempel

Check-begrænsningen kan forekomme stort set hvor som helst i tabeldefinitionen under oprettelsen. Selv på kolonneniveau kan det indstilles på en kolonne, der ikke er involveret i kontrollen overhovedet.

Her er et eksempel til illustration:

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

En INSERT for at teste begrænsningen:

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

Virker efter hensigten.

VALIDERING og IKKE GYLDIG

Vi har denne enkle tabel og data:

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

Antag, at vi nu skal implementere en Check-begrænsning, der forbyder værdier mindre end 50.

Forestil dig, at dette er et stort bord i produktion, og vi har ikke rigtig råd til nogen anskaffet lås i øjeblikket, som følge af en ALTER TABLE-erklæring. Men det er nødvendigt at få denne begrænsning på plads, fremadrettet.

ALTER TABLE får en lås (afhængig af hver anden underform). Som nævnt er denne tabel i produktion, så vi ønsker at vente til vi er ude af 'spidsbelastningstider '.

Du kan bruge INGEN GYLDIG valgmulighed, når du opretter Check-begrænsningen:

learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID; 
ALTER TABLE
Download Whitepaper Today PostgreSQL Management &Automation med ClusterControlFå flere oplysninger om, hvad du skal vide for at implementere, overvåge, administrere og skalere PostgreSQLDownload Whitepaper

Fortsatte operationer, hvis et forsøg på en INSERT eller OPDATERING, der overtræder Check-begrænsningen:

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

Værdien i kolonnen 'fornærmende' er forbudt.

Derefter, under nedetid, validerer vi Check-begrænsningen for at anvende den mod (enhver) allerede eksisterende kolonner, der kan være i strid:

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

Budskabet er efter min mening ret kryptisk. Men det informerer os om, at der er rækker, der ikke er i overensstemmelse med begrænsningen.

Her er nogle nøglepunkter, jeg ønskede at inkludere fra ALTER TABLE-dokumentationen (ordord direkte fra dokumenterne i anførselstegn):

  • Syntaks:ADD table_constraint [ IKKE GYLDIG ] - Medfølgende beskrivelse (delvis) "Denne formular tilføjer en ny begrænsning til en tabel ved hjælp af samme syntaks som CREATE TABLE, plus muligheden IKKE VALID, som i øjeblikket kun er tilladt for fremmednøgle og KONTROLLER begrænsninger. Hvis begrænsningen er markeret IKKE GYLDIG, springes den potentielt langvarige indledende kontrol for at bekræfte, at alle rækker i tabellen opfylder begrænsningen."
  • Syntaks:VALIDATE CONSTRAINT constraint_name - Medfølgende beskrivelse (delvis) "Denne formular validerer en fremmednøgle eller check constraint, der tidligere blev oprettet som IKKE VALID, ved at scanne tabellen for at sikre, at der ikke er nogen rækker, for hvilke begrænsningen ikke er opfyldt. " "Validering erhverver kun en DEL OPDATERING EKSKLUSIV lås på bordet, der ændres."

Som en sidebemærkning, to punkter værd at bemærke, jeg lærte undervejs. Set-returnerende funktioner og underforespørgsler er ikke tilladt i Check-begrænsningsdefinitioner. Jeg er sikker på, at der er andre, og jeg glæder mig over enhver feedback om dem i kommentarerne nedenfor.

Tjek begrænsninger er fantastiske. At bruge de 'indbyggede' løsninger, der leveres af selve PostgreSQL-databasen, til at håndhæve enhver databegrænsning(er) giver perfekt mening. Tid og kræfter brugt på at implementere Tjek begrænsninger for nødvendige kolonne(r), opvejer langt ikke at implementere nogen overhovedet. Dermed spares tid i det lange løb. Jo mere vi læner os op af databasen for at håndtere den slags krav, jo bedre. Gør det muligt for os at fokusere og anvende vores ressourcer til andre områder/aspekter af databasestyring.

Tak fordi du læste med.


  1. Hvordan kan du se, om en værdi ikke er numerisk i Oracle?

  2. Hvad du bør vide om MED NOCHECK, når du aktiverer en CHECK-begrænsning i SQL Server

  3. SQL Server:Nyttige tips til nybegyndere

  4. Oracle får fremmednøgler