Управлението на данни е голямо предизвикателство. Докато нашият свят се обръща, данните продължават да бъдат широко разпространени, изобилни и интензивни. Затова трябва да вземем мерки за справяне с притока.
Проверка на всяка отделна част от данни „на ръка ' денонощно е просто непрактично. Каква фантастична мечта. Но в края на краищата това е точно това. Една мечта. Лошите данни са лоши данни. Без значение как го нарязвате или нарязвате на кубчета (игра на думи). Това е проблем от самото начало, което води до още повече проблеми.
Съвременните бази данни се справят с голяма част от тежкия товар за нас. Много от тях предоставят вградени решения за подпомагане на управлението на тази конкретна област от данни.
Сигурен начин да контролирате данните, въведени в колоната на таблица, е с тип данни. Нуждаете се от колона с десетични числа, с общ брой цифри 4, с 2 от тези след десетичната запетая?
Дадено! Няма никакъв проблем.
NUMERIC(4,2), жизнеспособна опция, пази тази колона като куче пазач. Могат ли да се вмъкнат стойностите на символния текст? Няма шанс за снежна топка.
PostgreSQL предлага множество типове данни. Вероятно вече съществува такъв, който да задоволи вашите нужди. Ако не, можете да създадете свой собствен. (Вижте:PostgreSQL CREATE TYPE)
И все пак, самите типове данни не са достатъчни. Не можете да гарантирате, че най-специфичните изисквания са покрити и отговарят на такова широко структуриране. Правила за съответствие и някакъв вид „стандарт“ обикновено се изискват при проектирането на схема.
Да предположим, че в същата колона NUMERIC(4,2) искате само стойности, по-големи от 25,25, но по-малки от 74,33? В случай, че стойността 88.22 е съхранена, типът данни не е виновен. Като позволява 4 общи цифри, с най-много 2 след десетичната запетая, той върши своята работа. Сложете вината на друго място.
Как да спечелим на този фронт, когато става въпрос за контрол на данните, разрешени в нашата база данни? Последователността на данните е от изключителен приоритет и е неразделна част за всяко добро решение за данни. При (изключен) шанс да контролирате събраните данни от началото на източника им, последователността вероятно ще бъде по-малко проблем.
Но съвършен свят съществува (може би) само в един от онези много фентъзи романи, които обичам да чета.
За съжаление непълните, непоследователни и „мръсни“ данни са твърде често срещани характеристики и реалности, присъстващи в полето, насочено към база данни.
Въпреки това, не всичко е загубено в обреченост и мрак, тъй като имаме ограничения за проверка, за да смекчим тези проблеми. За тези специфични правила трябва да въведем по необходимост, което гарантира, че обработваме и съхраняваме само последователни данни. Като налагаме тези спецификации в базата данни, можем да сведем до минимум въздействието, което непоследователните данни оказват върху нашите бизнес цели и решения, които продължават напред.
Какво е ограничение? - Дефиниция от високо ниво
В този контекст ограничението е вид правило или ограничение, поставено върху колона на таблица на база данни. Тази специфика изисква данните, които влизат, да отговарят на зададените изисквания, преди да бъдат съхранени. Споменатите изисквания обикновено са „професионално“ измислени (и често са) като бизнес правила . Това се свежда до валидиращ булев тест за истинност. Ако данните преминат (вярно), те се съхраняват. Ако не, няма запис (невярно).
Ограничения, налични в PostgreSQL
Към момента на писане документацията на PostgreSQL изброява 6 категории ограничения.
Те са:
- Проверете ограниченията
- Ненулеви ограничения
- Уникални ограничения
- Първични ключове
- Външни ключове
- Ограничения за изключване
Проверете ограниченията
Един прост пример за колона INTEGER би бил да забраните стойности, по-големи от да речем, 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).
Както се вижда по-горе, опитите за INSERT всякакви стойности, които нарушават ограничението за проверка, се провалят.
Ограниченията за проверка не само наблюдават колоните по време на INSERT, дори операторите UPDATE (и други, например \copy и COPY) също трябва да се придържат към ограниченията.
Да предположим, че таблицата no_go има тази стойност:
learning=> TABLE no_go;
id
----
55
(1 row)
АКТУАЛИЗИРАНЕ на стойността на колоната id до такава, която не отговаря на ограничението за проверка, също е неуспешна:
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).
Ограниченията за проверка трябва да „имат смисъл“ за типа данни на целевата колона. Невалидно е да се опитвате и да ограничавате колона INTEGER, за да забраните съхраняването на текстови стойности, тъй като самият тип данни няма да го позволи.
Вижте този пример, където се опитвам да наложа този тип ограничение за проверка по време на създаване на таблица:
learning=> CREATE TABLE num_try(id INTEGER CHECK(id IN ('Bubble', 'YoYo', 'Jack-In-The-Box')));
ERROR: invalid input syntax for integer: "Bubble"
Живот без ограничения за проверка
Една стара поговорка, която чух, която резонира с мен, е:„Не пропускате водата, докато кладенецът не пресъхне . "
Без ограниченията на проверката, ние със сигурност можем да се свържем, тъй като тяхната забележителна полза е най-оценена, когато трябва да се справите без тях.
Вземете този пример...
За начало имаме тази таблица и данни, които представляват материали за повърхността на пътеката:
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)
И тази таблица с имена на пътеки и собствен 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)
Искаме да гарантираме, че следите на таблицата съдържат само surface_id за съответните стойности в таблица surface_material.
Да, да, знам. Ти ми крещиш.
„Не може ли това да се погрижи с ВЪНЖЕН КЛЮЧ?!?"
Да, може. Но го използвам, за да демонстрирам обща употреба, заедно с капан, който трябва да знам (споменат по-късно в публикацията).
Без ограниченията за проверка можете да прибягвате до TRIGGER и да предотвратите съхраняването на непоследователни стойности.
Ето един груб (но работещ) пример:
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();
Опитите за INSERT стойност, която няма съответен surface_id в следите на таблицата, неуспешни:
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
Резултатите от заявката по-долу потвърждават „нарушението ' стойността не беше съхранена:
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)
Това със сигурност е много работа за забрана на нежелани стойности.
Нека повторно приложим това изискване с ограничение Check.
Тъй като не можете да използвате подзаявка (ето защо използвах примера по-горе) в действителната дефиниция на ограничението за проверка, стойностите трябва да са твърдо кодирани .
За малка таблица или тривиален пример като представен тук, това е добре. В други сценарии, включващи повече стойности, може да е по-добре да потърсите алтернативно решение.
learning=> ALTER TABLE trails ADD CONSTRAINT t_check CHECK (surface_id IN (101, 202, 303, 404, 505, 606, 707, 808));
ALTER TABLE
Тук съм кръстил ограничението за проверка t_check срещу оставянето на системата да го име.
(Забележка:Дефинираният по-рано check_me() ФУНКЦИЯ и придружаваща TRIGGER бяха изпуснати (не са показани) преди стартирането на по-долу 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).
Бихте ли погледнали колко лесно беше това! Не са необходими TRIGER и FUNCTION.
Ограниченията за проверка правят този тип работа лесна.
Искате ли да станете хитри в дефиницията на ограничението за проверка?
Можеш.
Да предположим, че имате нужда от таблица с изброяване на пътеки, които са малко по-добри за тези с чувствителни глезени и колене. Тук не се изискват твърди повърхности.
Искате да сте сигурни, че всяка туристическа пътека или пътека, изброени в таблица nice_trail, има повърхностен материал от „чакъл“ или „мръсотия“.
Това ограничение за проверка се справя с това изискване без проблем:
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
Това абсолютно работи добре.
Но какво ще кажете за FUNCTION, която връща и двата идентификатора, необходими, за да може проверката да работи? Разрешена ли е FUNCTION в дефиницията на ограничението за проверка?
Да, един може да бъде включен.
Ето работещ пример.
Първо, тялото на функцията и дефиниция:
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;
Забележете, че в този израз CREATE TABLE дефинирам ограничението за проверка в „таблица ', докато преди съм предоставял само примери в 'колона ' ниво.
Проверете ограниченията, дефинирани на ниво таблица, са напълно валидни:
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
Всички тези вложки са добри:
learning=> INSERT INTO nice_trail(name, mat_surface_id)
learning-> VALUES ('Smooth Rock Loop', 101), ('High Water Bluff', 303);
INSERT 0 2
Сега идва INSERT за пътека, която не отговаря на ограничението за колона 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).
Нашето извикване FUNCTION в дефиницията на ограничението Check работи както е проектирано, ограничавайки нежеланите стойности на колоните.
Дим и огледала?
Всичко ли е както изглежда с ограниченията за проверка? Всичко черно и бяло? Няма фасада отпред?
Пример, който си струва да се отбележи.
Имаме проста таблица, в която искаме стойността ПО ПОДРАЗБИРАНЕ да бъде 10 за присъстващата единствена колона INTEGER:
learning=> CREATE TABLE surprise(id INTEGER DEFAULT 10, CHECK (id <> 10));
CREATE TABLE
Но също така включих ограничение за проверка, което забранява стойност от 10, като дефинирам id не може да бъде равен на това число.
Кой ще спечели деня? Ограничението DEFAULT или Check?
Може да се изненадате да разберете коя е.
Бях.
Произволно INSERT, работещо добре:
learning=> INSERT INTO surprise(id) VALUES(17);
INSERT 0 1
learning=> SELECT * FROM surprise;
id
----
17
(1 row)
И INSERT със стойността ПО ПОДРАЗБИРАНЕ:
learning=> INSERT INTO surprise(id) VALUES(DEFAULT);
ERROR: new row for relation "surprise" violates check constraint "surpise_id_check"
DETAIL: Failing row contains (10).
Опа...
Отново с алтернативен синтаксис:
learning=> INSERT INTO surprise DEFAULT VALUES;
ERROR: new row for relation "surprise" violates check constraint "surpise_id_check"
DETAIL: Failing row contains (10).
Ограничението Проверка печели над стойността ПО ПОДРАЗБИРАНЕ.
Пример за странна игра
Ограничението Check може да се появи почти навсякъде в дефиницията на таблицата по време на създаването. Дори на ниво колона, тя може да бъде зададена на колона, която изобщо не участва в проверката.
Ето пример за илюстрация:
learning=> CREATE TABLE mystery(id_1 INTEGER CHECK(id_2 > id_3),
learning(> id_2 INTEGER, id_3 INTEGER);
CREATE TABLE
INSERT за тестване на ограничението:
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).
Работи по предназначение.
ВАЛИДНО и НЕ ВАЛИДНО
Имаме тази проста таблица и данни:
learning=> CREATE TABLE v_check(id INTEGER);
CREATE TABLE
learning=> INSERT INTO v_check SELECT * FROM generate_series(1, 425);
INSERT 0 425
Да предположим, че сега трябва да приложим ограничение за проверка, което забранява всякакви стойности, по-малки от 50.
Представете си, че това е голяма таблица в производството и ние наистина не можем да си позволим никакво придобито заключване в момента, в резултат на оператор ALTER TABLE. Но трябва да поставите това ограничение на място, като продължите напред.
ALTER TABLE ще придобие заключване (в зависимост от всеки различен подформуляр). Както споменахме, тази таблица е в процес на производство, така че искаме да изчакаме, докато свършим „пиковите часове '.
Можете да използвате опцията NO VALID, когато създавате ограничението за проверка:
learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID;
ALTER TABLE
Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книга Продължаващи операции, ако се направи опит за INSERT или UPDATE, който нарушава ограничението за проверка:
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).
Стойността на колоната „нарушител“ е забранена.
След това, по време на престой, ние валидираме ограничението за проверка, за да го приложим спрямо (всякакви) съществуващи колони, които може да са в нарушение:
learning=> ALTER TABLE v_check VALIDATE CONSTRAINT fifty_chk;
ERROR: check constraint "fifty_chk" is violated by some row
Според мен съобщението е доста загадъчно. Но ни информира, че има редове, които не са в съответствие с ограничението.
Ето някои ключови точки, които исках да включа от документацията на ALTER TABLE (Дългословие директно от документите в кавички):
- Синтаксис:ADD table_constraint [ НЕ ВАЛИДНО ] – Придружаващо описание (частично) „Този формуляр добавя ново ограничение към таблица, използвайки същия синтаксис като CREATE TABLE, плюс опцията НЕ ВАЛИДНА, която в момента е разрешена само за външен ключ и ПРОВЕРЕТЕ ограниченията. Ако ограничението е отбелязано НЕВАЛИДНО, потенциално дългата първоначална проверка, за да се потвърди дали всички редове в таблицата отговарят на ограничението, се пропуска."
- Синтаксис:VALIDATE CONSTRAINT име_на_ограничението – Придружаващо описание (частично) „Този формуляр потвърждава външен ключ или ограничение за проверка, което преди това е създадено като НЕ ВАЛИДНО, като сканира таблицата, за да се увери, че няма редове, за които ограничението не е изпълнено. " „Валидирането придобива само заключване на ИЗКЛЮЧИТЕЛНО АКТУАЛИЗИРАНЕ НА СПОДЕЛЯНЕ на таблицата, която се променя.“
Като настрана, две точки, които си струва да отбележа, научих по пътя. Функциите и подзаявките, връщащи набори, не са разрешени в Проверете дефинициите на ограниченията. Сигурен съм, че има и други и приветствам всяка обратна връзка за тях в коментарите по-долу.
Ограниченията за проверка са страхотни. Използването на „вградените“ решения, предоставени от самата база данни PostgreSQL, за налагане на всякакви ограничения(я) на данни е напълно логично. Времето и усилията, изразходвани за внедряване. Проверете ограниченията за необходимата(и) колона(и), далеч надвишава липсата на внедряване на никакви. По този начин спестява време в дългосрочен план. Колкото повече разчитаме на базата данни, за да се справим с тези видове изисквания, толкова по-добре. Позволява ни да фокусираме и прилагаме нашите ресурси към други области/аспекти на управление на база данни.
Благодаря ви, че прочетохте.