sql >> Databasteknik >  >> RDS >> PostgreSQL

Förstå kontrollbegränsningar i PostgreSQL

Att hantera data är en stor utmaning. När vår värld vänder fortsätter data att vara utbredd, riklig och intensiv. Därför måste vi vidta åtgärder för att hantera tillströmningen.

Validerar varje enskild databit för hand ' dygnet runt är helt enkelt opraktiskt. Vilken fantastisk dröm. Men trots allt är det just det. En dröm. Dålig data är dålig data. Oavsett hur du skivar det eller tärnar det (pun intended). Det är ett problem från början, vilket leder till ännu fler problem.

Moderna databaser hanterar mycket av de tunga lyften för oss. Många tillhandahåller inbyggda lösningar för att hjälpa till att hantera just detta dataområde.

Ett säkert sätt att kontrollera data som matas in i en tabells kolumn är med en datatyp. Behöver du en kolumn med decimaltal, med ett totalt antal siffror på 4, med två av dem efter decimalen?

Visst! Inga problem alls.

NUMERIC(4,2), ett genomförbart alternativ, bevakar den kolumnen som en vakthund. Kan teckentextvärden glida in där? Inte en snöbolls chans.

PostgreSQL erbjuder en mängd olika datatyper. Chansen är stor att en redan finns för att tillfredsställa dina behov. Om inte kan du skapa din egen. (Se:PostgreSQL SKAPA TYP)

Ändå räcker det inte med enbart datatyper. Du kan inte garantera att de mest specifika kraven täcks och överensstämmer med en sådan bred strukturering. Överensstämmelseregler och någon sorts "standard" krävs vanligtvis när man utformar ett schema.

Anta att du i samma NUMERIC(4,2) kolumn bara vill ha värden större än 25,25 men mindre än 74,33? I händelse av att värdet 88.22 lagras, är det inte fel på datatypen. Genom att tillåta 4 totala siffror, med högst 2 efter decimalen, gör den sitt jobb. Lägg skulden någon annanstans.

Hur vinner vi på denna front när det gäller att kontrollera den data som tillåts i vår databas? Datakonsistens är av högsta prioritet och är en integrerad del av alla sunda datalösningar. Om (av) chansen att du kontrollerade den insamlade informationen från början av ursprungskällan, skulle konsekvens sannolikt vara mindre av ett problem.

Men en perfekt värld finns (kanske) bara i en av de många fantasyromaner jag älskar att läsa.

Tyvärr är ofullständiga, inkonsekventa och "smutsiga" data alltför vanliga egenskaper och realiteter som finns i ett databascentrerat fält.

Men allt är inte förlorat i undergång och dysterhet för vi har Check-begränsningar för att mildra dessa problem. För dessa specifika regler måste vi införa, av nödvändighet, som säkerställer att vi hanterar och lagrar endast konsekventa data. Genom att införa dessa specifikationer i databasen kan vi minimera den påverkan som inkonsekventa data har på våra affärsmål och lösningar i fortsättningen.

Vad är en begränsning? - En definition på hög nivå

I detta sammanhang är en begränsning en typ av regel eller begränsning som placeras på en databastabellkolumn. Denna specificitet kräver att data som kommer in måste uppfylla de uppställda kraven innan de lagras. Nämnda krav tenderar att vara "professionellt" myntade (och är ofta) som affärsregler . Detta kokar ner till ett booleskt valideringstest för sanning. Om data passerar (sant) lagras den. Om inte, ingen inmatning (falskt).

Begränsningar tillgängliga i PostgreSQL

I skrivande stund listar PostgreSQL-dokumentationen 6 kategorier av begränsningar.

De är:

  • Kontrollera begränsningar
  • Inte nollbegränsningar
  • Unika begränsningar
  • Primära nycklar
  • Främmande nycklar
  • Uteslutningsbegränsningar

Kontrollera begränsningar

Ett enkelt exempel för en INTEGER-kolumn skulle vara att inte tillåta värden större än säg 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 framgår av ovan misslyckas försök att INFOGA värden som bryter mot kontrollbegränsningen.

Kontrollera begränsningar övervakar inte bara kolumner under INSERT, även UPDATE-satser (och andra, t.ex. \copy och COPY) måste också följa begränsningarna.

Anta att no_go-tabellen har detta värde:

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

En UPPDATERING av id-kolumnvärdet till ett som inte överensstämmer med Check-begränsningen misslyckas också:

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

Kontrollera begränsningar måste "göra meningsfullt" för målkolumnen datatyp. Det är ogiltigt att försöka begränsa en INTEGER-kolumn för att förbjuda lagring av textvärden eftersom datatypen i sig inte tillåter det.

Se det här exemplet där jag försöker införa den typen av kontrollbegränsning under tabellskapandet:

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

Livet utan checkbegränsningar

Ett gammalt talesätt jag har hört som resonerar med mig är:"Du missar inte vattnet förrän brunnen är torr . "

Utan Check-begränsningar kan vi säkert relatera till deras anmärkningsvärda nytta som är mest uppskattad när du måste klara dig utan dem.

Ta det här exemplet...

Till att börja med har vi den här tabellen och data som representerar spårytmaterial:

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)

Och den här tabellen med spårnamn och sitt eget yta_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 vill försäkra oss om att tabellspår endast innehåller yta_id för motsvarande värden i tabellen ytmaterial.

Ja ja jag vet. Du skriker på mig.

"Kan inte detta skötas med en UTLÄNDSK NYCKEL?!?"

Ja, det kan det. Men jag använder det för att demonstrera en generisk användning, tillsammans med en fallgrop att känna till (nämns senare i inlägget).

Utan Check-begränsningar kan du använda en TRIGGER och förhindra att inkonsekventa värden lagras.

Här är ett grovt (men fungerande) exempel:

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

Försök att INFOGA ett värde som inte har ett motsvarande yt-id i tabellspår, misslyckas:

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

Frågeresultaten nedan bekräftar "kränkande ' värde lagrades inte:

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 är verkligen mycket arbete för att förbjuda oönskade värden.

Låt oss återimplementera detta krav med en Check-begränsning.

Eftersom du inte kan använda en underfråga (det är därför jag använde exemplet ovan) i den faktiska Check-begränsningsdefinitionen, måste värdena vara hårdkodade .

För ett litet bord, eller triviala exempel som presenteras här, är det bra. I andra scenarier, genom att införliva fler värderingar, kan du vara bättre tjänt av att söka 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

Här har jag döpt Check-begränsningen till t_check kontra att låta systemet namnge den.

(Obs! Den tidigare definierade check_me() FUNKTION och medföljande TRIGGER borttogs (visas inte) innan du körde nedanstående INFOGA.)

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

Vill du se hur lätt det var! Ingen TRIGGER och FUNKTION behövs.

Kontrollera begränsningar gör den här typen av arbete lätt.

Vill du bli smart i definitionen av Check-begränsning?

Du kan.

Anta att du behöver en tabell med spår som är lite snällare mot de med känsliga anklar och knän. Inga hårda ytor önskas här.

Du vill försäkra dig om att alla vandringsleder eller spår som anges i tabellen nice_trail har ett ytmaterial av antingen 'Grus' eller 'Smuts'.

Denna kontrollbegränsning hanterar det kravet inga problem:

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 fungerar absolut bra.

Men vad sägs om en FUNKTION som returnerar båda id:n som krävs för att checken ska fungera? Är en FUNKTION tillåten i Check-begränsningsdefinitionen?

Ja, en kan inkorporeras.

Här är ett fungerande exempel.

Upp först, funktionskroppen och 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;

Observera att i denna CREATE TABLE-sats definierar jag kontrollbegränsningen i tabellen ' nivå medan jag tidigare bara har gett exempel i 'kolumnen nivå.

Kontrollbegränsningar som definierats på tabellnivå är helt giltiga:

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

Dessa insatser är alla bra:

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

Nu följer en INSERT för ett spår som inte uppfyller begränsningen på kolumn 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).

Vårt FUNCTION-anrop i Check-begränsningsdefinitionen fungerar som det är tänkt, vilket begränsar de oönskade kolumnvärdena.

Rök och speglar?

Är allt som det ser ut med Check-begränsningar? Allt svart och vitt? Ingen fasad på framsidan?

Ett exempel värt att notera.

Vi har en enkel tabell där vi vill att DEFAULT-värdet ska vara 10 för den enda HELTAL-kolumnen som finns:

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

Men jag har också inkluderat en kontrollbegränsning som förbjuder ett värde på 10, genom att definiera id kan inte vara lika med det numret.

Vilken vinner dagen? STANDARD- eller kontrollbegränsningen?

Du kanske blir förvånad över att veta vilken det är.

Jag var.

En godtycklig INSERT, fungerar bra:

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

Och en INSERT med DEFAULT-värdet:

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

Hoppsan...

Återigen, med en alternativ syntax:

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 vinner över DEFAULT-värdet.

Oudball-exempel

Check-begränsningen kan visas i stort sett var som helst i tabelldefinitionen under skapandet. Även på kolumnnivå kan den ställas in på en kolumn som inte är inblandad i kontrollen överhuvudtaget.

Här är ett exempel för att illustrera:

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

En INSERT för att testa 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).

Fungerar som tänkt.

VALIDERING och INTE GILTIG

Vi har denna enkla tabell och data:

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

Anta att vi nu behöver implementera en kontrollbegränsning som förbjuder alla värden mindre än 50.

Föreställ dig att det här är ett stort bord i produktion och vi har inte riktigt råd med något köpt lås för tillfället, ett resultat av ett ALTER TABLE-uttalande. Men, måste få denna begränsning på plats, framåt.

ALTER TABLE kommer att få ett lås (beroende på varje underform). Som nämnts är denna tabell under produktion, så vi vill vänta tills vi är ute efter "högtid '.

Du kan använda alternativet INGEN GILTIGT när du skapar kontrollbegränsningen:

learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID; 
ALTER TABLE
Ladda ner Whitepaper Today PostgreSQL Management &Automation med ClusterControlLäs om vad du behöver veta för att distribuera, övervaka, hantera och skala PostgreSQLDladda Whitepaper

Fortsatt verksamhet, om ett försök till en INFOGA eller UPPDATERING som bryter mot kontrollbegrä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ärdet på kolumnen "kränkande" är förbjudet.

Sedan, under driftstopp, validerar vi Check-begränsningen för att tillämpa den mot (alla) redan existerande kolumner som kan bryta mot:

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

Budskapet är ganska kryptiskt enligt mig. Men det informerar oss om att det finns rader som inte följer begränsningen.

Här är några viktiga punkter som jag ville ta med från ALTER TABLE-dokumentationen (Verbiage direkt från dokumenten inom citattecken):

  • Syntax:ADD table_constraint [ NOT VALID ] - Medföljande beskrivning (delvis) "Det här formuläret lägger till en ny restriktion till en tabell med samma syntax som CREATE TABLE, plus alternativet NOT VALID, som för närvarande endast är tillåtet för främmande nyckel och KONTROLLERA begränsningar. Om begränsningen är markerad som NOT GILTIG, hoppas den potentiellt långa första kontrollen för att verifiera att alla rader i tabellen uppfyller begränsningen över."
  • Syntax:VALIDATE CONSTRAINT constraint_name - Medföljande beskrivning (delvis) "Det här formuläret validerar en främmande nyckel eller kontrollrestriktion som tidigare skapats som NOT VALID, genom att skanna tabellen för att säkerställa att det inte finns några rader för vilka begränsningen inte är uppfylld. " "Validering erhåller endast ett EXKLUSIVT LÅS FÖR DELA UPPDATERING på bordet som ändras."

Som ett stycke, två punkter värda att notera jag lärde mig på vägen. Setreturerande funktioner och underfrågor är inte tillåtna i Check-begränsningsdefinitioner. Jag är säker på att det finns andra och jag välkomnar all feedback om dem i kommentarerna nedan.

Kontrollera begränsningar är fantastiska. Att använda de "inbyggda" lösningarna som tillhandahålls av självaste PostgreSQL-databasen, för att upprätthålla eventuella databegränsningar, är helt logiskt. Tid och ansträngning som spenderas på att implementera Kontrollera begränsningar för nödvändiga kolumner, uppväger vida att inte implementera några alls. På så sätt sparar du tid i längden. Ju mer vi lutar oss mot databasen för att hantera den här typen av krav, desto bättre. Gör det möjligt för oss att fokusera och tillämpa våra resurser på andra områden/aspekter av databashantering.

Tack för att du läser.


  1. Oracle - uppdatera join - icke nyckelbevarad tabell

  2. MySQL INSERT INTO table VALUES.. vs INSERT INTO table SET

  3. Hur DAYOFMONTH() fungerar i MariaDB

  4. Neo4j - Skapa en nod med Cypher