PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Zrozumienie ograniczeń sprawdzania w PostgreSQL

Zarządzanie danymi to duże wyzwanie. Gdy nasz świat się kręci, dane nadal są szeroko rozpowszechnione, obfite i intensywne. Dlatego musimy podjąć środki, aby poradzić sobie z napływem.

Weryfikowanie każdego elementu danych „ręcznie „przez całą dobę jest po prostu niepraktyczne. Co za fantastyczny sen. Ale w końcu tak właśnie jest. Sen. Złe dane to złe dane. Bez względu na to, jak go pokroisz lub pokroisz w kostkę (gra słów zamierzona). Jest to problem od samego początku, prowadzący do jeszcze większej liczby problemów.

Nowoczesne bazy danych obsługują większość ciężkich prac dla nas. Wiele z nich zapewnia wbudowane rozwiązania, które pomagają w zarządzaniu tym konkretnym obszarem danych.

Pewnym sposobem kontrolowania danych wprowadzanych do kolumny tabeli jest typ danych. Potrzebujesz kolumny z liczbami dziesiętnymi o łącznej liczbie cyfr 4, z 2 z nich po przecinku?

Jasne! Żaden problem.

NUMERIC(4,2), realna opcja, chroni tę kolumnę jak strażnik. Czy wartości tekstu znaków mogą się tam wślizgnąć? Nie ma szans na śnieżkę.

PostgreSQL oferuje wiele typów danych. Są szanse, że jeden już istnieje, aby zaspokoić twoje potrzeby. Jeśli nie, możesz stworzyć własny. (Patrz:PostgreSQL CREATE TYPE)

Jednak same typy danych nie wystarczą. Nie można zapewnić, że najbardziej szczegółowe wymagania zostaną uwzględnione i zgodne z tak szeroką strukturą. Podczas projektowania schematu zazwyczaj wymagane są zasady zgodności i pewien rodzaj „standardu”.

Załóżmy, że w tej samej kolumnie NUMERIC(4,2) chcesz tylko wartości większe niż 25,25, ale mniejsze niż 74,33? W przypadku zapisania wartości 88,22 typ danych nie jest uszkodzony. Dopuszczając 4 cyfry, z maksymalnie 2 po przecinku, wykonuje swoje zadanie. Zrzuć winę gdzie indziej.

Jak wygrywamy na tym froncie, jeśli chodzi o kontrolę danych dozwolonych w naszej bazie danych? Spójność danych ma najwyższy priorytet i jest integralną częścią każdego rozwiązania dotyczącego danych dźwiękowych. W przypadku (wyłączenia) możliwości kontrolowania zebranych danych od początku ich źródła, spójność prawdopodobnie byłaby mniejszym problemem.

Ale idealny świat istnieje (być może) tylko w jednej z wielu powieści fantasy, które uwielbiam czytać.

Niestety niekompletne, niespójne i „brudne” dane są zbyt powszechnymi cechami i rzeczywistością występującą w dziedzinie skoncentrowanej na bazie danych.

Jednak nie wszystko jest stracone w zagładzie i mroku, ponieważ mamy ograniczenia Check, aby złagodzić te problemy. W przypadku tych konkretnych zasad musimy wprowadzić z konieczności, które zapewnią, że przetwarzamy i przechowujemy tylko spójne dane. Wyznaczając te specyfikacje w bazie danych, możemy zminimalizować wpływ niespójnych danych na nasze cele biznesowe i dalsze rozwiązania.

Co to jest ograniczenie? - Definicja wysokiego poziomu

W tym kontekście ograniczenie jest rodzajem reguły lub ograniczenia nałożonego na kolumnę tabeli bazy danych. Ta specyfika wymaga, aby napływające dane były zgodne z ustalonymi wymaganiami przed ich przechowywaniem. Wspomniane wymagania są zwykle „profesjonalnie” ukute (i często są) jako zasady biznesowe . Sprowadza się to do walidacyjnego testu logicznego na prawdę. Jeśli dane przechodzą (prawda), są przechowywane. Jeśli nie, brak wpisu (fałsz).

Ograniczenia dostępne w PostgreSQL

W chwili pisania tego tekstu dokumentacja PostgreSQL wymienia 6 kategorii ograniczeń.

Są to:

  • Sprawdź ograniczenia
  • Ograniczenia niezerowe
  • Unikalne ograniczenia
  • Klucze główne
  • Klucze obce
  • Ograniczenia wykluczenia

Sprawdź ograniczenia

Prostym przykładem dla kolumny INTEGER byłoby zabronienie wartości większych niż powiedzmy 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).

Jak widać powyżej, próby WSTAWIENIA wartości, które naruszają ograniczenie Sprawdź, nie powiodą się.

Sprawdź ograniczenia nie tylko monitoruj kolumny podczas INSERT, nawet instrukcje UPDATE (i inne, np. \copy i COPY) muszą również przestrzegać ograniczeń.

Załóżmy, że tabela no_go ma następującą wartość:

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

AKTUALIZACJA wartości kolumny id na taką, która nie jest zgodna z ograniczeniem Sprawdź również kończy się niepowodzeniem:

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

Ograniczenia sprawdzające muszą mieć „sens” dla typu danych kolumny docelowej. Nieprawidłowe jest próbowanie i ograniczanie kolumny INTEGER, aby zabronić przechowywania wartości tekstowych, ponieważ sam typ danych na to nie pozwala.

Zobacz ten przykład, w którym próbuję narzucić tego typu ograniczenie Check podczas tworzenia tabeli:

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

Życie bez ograniczeń kontrolnych

Stare powiedzenie, które słyszałem, które do mnie pasuje, brzmi:„Nie tęsknisz za wodą, dopóki studnia nie wyschnie . "

Bez ograniczeń Check, z pewnością możemy się odnieść, ponieważ ich niezwykła korzyść jest najbardziej doceniana, gdy musisz się bez nich obejść.

Weźmy ten przykład…

Na początek mamy poniższą tabelę i dane przedstawiające materiały powierzchni szlaku:

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)

I ta tabela z nazwami szlaków i własnym identyfikatorem powierzchni:

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)

Chcemy zapewnić, że ślady tabeli zawierają tylko identyfikatory powierzchni dla odpowiednich wartości w tabeli surface_material.

Tak tak wiem. Krzyczysz na mnie.

Nie można się tym zająć za pomocą KLUCZ OBCY?!?"

Tak, może. Ale używam go, aby zademonstrować ogólne zastosowanie, wraz z pułapką, o której warto wiedzieć (wspomnianą później w poście).

Bez ograniczeń Check możesz skorzystać z TRIGGERa i zapobiec przechowywaniu niespójnych wartości.

Oto prosty (ale działający) przykład:

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

Próba WSTAWIENIA wartości, która nie ma odpowiadającego identyfikatora powierzchni w śladach tabeli, kończy się niepowodzeniem:

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

Poniższe wyniki zapytania potwierdzają „przestępstwo ' wartość nie została zapisana:

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)

To z pewnością dużo pracy, aby zabronić niechcianych wartości.

Zaimplementujmy ponownie to wymaganie za pomocą ograniczenia Sprawdź.

Ponieważ nie możesz użyć podzapytania (tutaj użyłem powyższego przykładu) w rzeczywistej definicji ograniczenia Sprawdź, wartości muszą być zakodowane na stałe .

W przypadku małego stolika lub trywialnego przykładu, takiego jak przedstawiony tutaj, jest to w porządku. W innych scenariuszach, obejmujących więcej wartości, może być lepiej, gdybyś poszukał alternatywnego rozwiązania.

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

Tutaj nazwałem ograniczenie sprawdzające t_check kontra pozwolenie systemowi na nadanie mu nazwy.

(Uwaga:wcześniej zdefiniowana FUNKCJA check_me() i towarzysząca TRIGGER zostały usunięte (nie pokazano) przed uruchomieniem poniższych WSTAW.)

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

Spójrz, jakie to było łatwe! Nie jest potrzebny SPUST ani FUNKCJA.

Sprawdź ograniczenia sprawiają, że tego typu praca jest łatwa.

Chcesz być przebiegły w sprawdzaniu definicji ograniczenia?

Możesz.

Załóżmy, że potrzebujesz tabeli z listą szlaków, które są nieco łagodniejsze dla osób o wrażliwych kostkach i kolanach. Tutaj nie są pożądane twarde powierzchnie.

Chcesz mieć pewność, że każdy szlak turystyczny lub ścieżka wymieniona w tabeli nice_trail ma powierzchnię „żwiru” lub „brudu”.

To ograniczenie sprawdzające obsługuje to wymaganie bez problemu:

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

To absolutnie działa dobrze.

Ale co powiesz na FUNKCJĘ, która zwraca oba identyfikatory wymagane do działania czeku? Czy FUNKCJA jest dozwolona w definicji ograniczenia Sprawdź?

Tak, można go włączyć.

Oto działający przykład.

Najpierw treść funkcji i definicja:

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;

Zauważ, że w tej instrukcji CREATE TABLE definiuję ograniczenie Check w 'tabeli ', podczas gdy wcześniej podałem przykłady tylko w 'kolumnie poziom.

Sprawdź, czy ograniczenia zdefiniowane na poziomie tabeli są całkowicie poprawne:

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

Wszystkie te wstawki są dobre:

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

Teraz pojawia się INSERT dla szlaku, który nie spełnia ograniczenia w kolumnie 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).

Nasze wywołanie FUNKCJI w definicji ograniczenia Sprawdź działa zgodnie z założeniami, ograniczając niepożądane wartości kolumn.

Dym i lustra?

Czy wszystko jest tak, jak się wydaje z ograniczeniami Sprawdź? Wszystko czarno-białe? Brak elewacji z przodu?

Przykład wart uwagi.

Mamy prostą tabelę, w której chcemy, aby wartość DEFAULT wynosiła 10 dla pojedynczej kolumny INTEGER:

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

Ale dodałem również ograniczenie Sprawdź, które zabrania wartości 10, definiując id nie może być równe tej liczbie.

Który wygra ten dzień? Ograniczenie DOMYŚLNE czy Sprawdź?

Możesz być zaskoczony tym, który to jest.

byłem.

Dowolna INSERT, działa dobrze:

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

I INSERT z wartością DOMYŚLNĄ:

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

Ponownie, z alternatywną składnią:

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

Ograniczenie Sprawdź wygrywa z wartością DOMYŚLNĄ.

Przykład dziwaka

Ograniczenie Sprawdź może pojawić się praktycznie w dowolnym miejscu w definicji tabeli podczas tworzenia. Nawet na poziomie kolumny można go ustawić na kolumnie, która nie jest w żaden sposób objęta kontrolą.

Oto przykład ilustrujący:

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

INSERT, aby przetestować ograniczenie:

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

Działa zgodnie z przeznaczeniem.

WALIDACJA i NIEWAŻNE

Mamy prostą tabelę i dane:

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

Załóżmy, że teraz musimy zaimplementować ograniczenie Check, które zabrania wszelkich wartości mniejszych niż 50.

Wyobraź sobie, że jest to duża tabela w produkcji i tak naprawdę nie stać nas w tej chwili na jakąkolwiek nabytą blokadę, wynikającą z instrukcji ALTER TABLE. Ale musisz wprowadzić to ograniczenie, idąc do przodu.

ALTER TABLE uzyska blokadę (zależną od każdego podformularza). Jak wspomniano, ta tabela jest w produkcji, więc chcemy poczekać, aż minie „godziny szczytu '.

Możesz użyć opcji BRAK WAŻNOŚCI podczas tworzenia ograniczenia Sprawdź:

learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID; 
ALTER TABLE
Pobierz oficjalny dokument już dziś Zarządzanie i automatyzacja PostgreSQL za pomocą ClusterControlDowiedz się, co musisz wiedzieć, aby wdrażać, monitorować, zarządzać i skalować PostgreSQLPobierz oficjalny dokument

Kontynuacja operacji, jeśli próba INSERT lub UPDATE narusza ograniczenie Check:

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

Wartość kolumny „obraźliwe” jest zabroniona.

Następnie, podczas przestoju, weryfikujemy ograniczenie Sprawdź, aby zastosować je do (wszelkich) istniejących wcześniej kolumn, które mogą naruszać:

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

Wiadomość jest moim zdaniem dość tajemnicza. Ale informuje nas, że istnieją wiersze niezgodne z ograniczeniem.

Oto kilka kluczowych punktów, które chciałem zawrzeć w dokumentacji ALTER TABLE (weryfikacja bezpośrednio z dokumentacji w cudzysłowie):

  • Składnia:ADD table_constraint [ NOT VALID ] — towarzyszący opis (częściowy) „Ten formularz dodaje nowe ograniczenie do tabeli przy użyciu tej samej składni co CREATE TABLE, plus opcja NOT VALID, która jest obecnie dozwolona tylko dla klucza obcego i Ograniczenia SPRAWDŹ. Jeśli ograniczenie jest oznaczone jako NIEPRAWIDŁOWE, potencjalnie długie wstępne sprawdzanie, czy wszystkie wiersze w tabeli spełniają ograniczenie, jest pomijane.
  • Składnia:VALIDATE CONSTRAINT nazwa_ograniczenia — towarzyszący opis (częściowy) „Ten formularz sprawdza poprawność klucza obcego lub ograniczenia kontrolnego, które zostało wcześniej utworzone jako NIEPRAWIDŁOWE, poprzez skanowanie tabeli w celu upewnienia się, że nie ma wierszy, dla których ograniczenie nie jest spełnione. " „Weryfikacja uzyskuje tylko blokadę SHARE UPDATE EXCLUSIVE na zmienianej tabeli”.

Na marginesie, dwie rzeczy warte odnotowania, których nauczyłem się po drodze. Funkcje zwracające zestaw i podzapytania nie są dozwolone w definicjach ograniczeń sprawdzania. Jestem pewien, że są inni i cieszę się z wszelkich opinii na ich temat w komentarzach poniżej.

Sprawdź ograniczenia są niesamowite. Korzystanie z „wbudowanych” rozwiązań dostarczanych przez samą bazę danych PostgreSQL w celu wymuszenia wszelkich ograniczeń danych ma sens. Czas i wysiłek włożony w implementację Sprawdź ograniczenia dla niezbędnych kolumn, znacznie przewyższają brak implementacji w ogóle. W ten sposób oszczędzamy czas na dłuższą metę. Im bardziej opieramy się na bazie danych, aby obsłużyć tego rodzaju wymagania, tym lepiej. Umożliwiając nam skupienie się i zastosowanie naszych zasobów w innych obszarach/aspektach zarządzania bazami danych.

Dziękuję za przeczytanie.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wdrażanie i konfiguracja PostgreSQL za pomocą Puppet

  2. Użytkownicy aplikacji a zabezpieczenia na poziomie wiersza

  3. Pandy zapisują ramkę danych do innego schematu postgresql

  4. Uzyskać kolejność ostatnich N wierszy w bazie danych?

  5. Jak uzyskać aktualny czas (bez strefy czasowej) w PostgreSQL