Слогер Создать блог
Разработка

CHECK-ограничение: документация, которая кусается

Как неявное ограничение в PostgreSQL сломало авторегистрацию контактов — и чему это учит.

CHECK-ограничение — это документация с зубами.

Вот и весь урок. Но давайте посмотрим, как он усваивается не во время чтения, а во время выполнения.

Что произошло

Мы используем ARIA — автономную CRM и систему автоматизации в Elevare Digital. Новые контакты автоматически попадают в последовательности действий — без участия человека.

В какой-то момент новые записи перестали регистрироваться. Родительская операция (создание контакта) полностью отменялась. Ни последовательности, ни контакта. И никакой полезной ошибки для вызывающего кода.

Виновником оказалась колонка region с CHECK-ограничением:

ALTER TABLE enrollment_tracker ADD CONSTRAINT region_allowed CHECK (region IN ('north', 'south', 'east', 'west', 'central'));

Таблица была создана для одной конкретной программы, охватывающей пять регионов. Позже код начал использовать её как общий трекер регистрации, передавая значения, которых ограничение не ожидало. PostgreSQL отклонил вставку. А поскольку вставка происходила внутри триггера, вся родительская транзакция откатилась.

Колонка называлась region. В названии нет намёка на то, что допустимы только пять значений. Ограничение это говорило. Но никто его не прочитал.

Почему триггер усугубляет ситуацию

Если вставлять напрямую в таблицу с нарушением CHECK, вы получите ошибку сразу. Раздражает, но локализовано.

Когда вставка происходит внутри триггера на другой таблице, ошибка всплывает и прерывает операцию, вызвавшую триггер. Вызывающий код видит, что его запись не удалась. Он может даже не знать о существовании триггера, не говоря уже о том, какое ограничение сработало внутри.

-- Триггер срабатывает при INSERT в contacts CREATE OR REPLACE FUNCTION enroll_contact() RETURNS trigger AS $$ BEGIN -- Этот INSERT может подорвать родительский INSERT INTO contacts INSERT INTO enrollment_tracker (contact_id, region, enrolled_at) VALUES (NEW.id, NEW.region, now()); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_enroll AFTER INSERT ON contacts FOR EACH ROW EXECUTE FUNCTION enroll_contact();

Теперь попробуйте это:

INSERT INTO contacts (id, name, region) VALUES (gen_random_uuid(), 'Acme Corp', 'southeast'); -- ERROR: new row for relation "enrollment_tracker" violates -- check constraint "region_allowed" -- DETAIL: Failing row contains (..., southeast, ...). -- Контакт НЕ создан.

southeast — вполне допустимая бизнес-сущность. Просто таблица о ней не знала.

Исправление: проверяйте до вставки

Поняв ограничение, мы легко его исправили. Проверяем допустимые значения в коде (или в самом триггере) перед попыткой вставки. Если значение не разрешено, пропускаем запись о регистрации и логируем. Родительская операция завершается успешно.

CREATE OR REPLACE FUNCTION enroll_contact() RETURNS trigger AS $$ DECLARE allowed_regions TEXT[] := ARRAY['north', 'south', 'east', 'west', 'central']; BEGIN IF NEW.region = ANY(allowed_regions) THEN INSERT INTO enrollment_tracker (contact_id, region, enrolled_at) VALUES (NEW.id, NEW.region, now()); ELSE -- Логируем, не прерывая родительскую запись INSERT INTO enrollment_skipped (contact_id, region, skipped_at, reason) VALUES (NEW.id, NEW.region, now(), 'region not in enrollment_tracker allowed list'); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql;

Альтернативно, можно динамически получать список из определения ограничения — это удобно, если значения могут расширяться:

SELECT consrc FROM pg_constraint WHERE conname = 'region_allowed' AND conrelid = 'enrollment_tracker'::regclass;

Вернёт что-то вроде (region = ANY (ARRAY['north'::text, 'south'::text, ...])). Не самый чистый парсинг, но точно показывает, что задумано схемой.

Как читать унаследованные ограничения

Перед записью в любую таблицу, которую вы не создавали:

-- psql shortcut \d enrollment_tracker -- Или прямой запрос SELECT tc.constraint_name, tc.constraint_type, cc.check_clause FROM information_schema.table_constraints tc LEFT JOIN information_schema.check_constraints cc ON tc.constraint_name = cc.constraint_name WHERE tc.table_name = 'enrollment_tracker';

Так вы найдёте ограничения:

  • CHECK — допустимые значения, диапазоны, межколоночные правила
  • UNIQUE — уникальность, которую вы могли не предполагать
  • NOT NULL — обязательные колонки
  • FOREIGN KEY — ссылочные зависимости

Всё это не скрыто. Просто редко читается.

Настоящий урок

Таблица называлась enrollment_tracker. Звучит универсально. Но она была создана для конкретной программы с пятью регионами, и CHECK-ограничение было единственным местом, где эта область действия была зафиксирована.

Когда поздний код начал использовать её как универсальный трекер, он привнёс предположение, о котором не знал. Схема проявила это предположение в момент записи, внутри триггера, и это уничтожило родительскую запись.

Ограничения схемы — это самая близкая к обязательной документация, которая есть у большинства баз данных. Они не устаревают. Их не забывают в вики. Они принудительно выполняются.

Читайте их до записи. А не после.

— Майк Кларк, основатель Elevare Digital.

По материалам: dev.to. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

Комментарии (0)

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.

Маркетинг

Как запустить продукт на маленькой базе подписчиков: 5 писем, которые продают без фальшивого дефицита

Одно письмо-анонс почти никогда ничего не продаёт — даже на списке из 40 человек. Разбираем пятидневный ритм рассылки, где каждое письмо решает свою задачу, и даём готовый шаблон последнего письма.

Слогер 28.10.2026 ▲ 1
Разработка

Как выгрузить вакансии и зарплатные вилки из Greenhouse API: разбор подводных камней

Любая доска Greenhouse отдаётся публичным JSON без ключа — целиком, одним ответом. Но чтобы данные были пригодны, нужно знать пять вещей про флаги, экранирование и двойной счёт вакансий.

Слогер 28.10.2026 ▲ 0
Fluxs lenta
Реклама · fluxs.ru
Маркетинг

Переезд (миграция) с Битрикс24 и amoCRM без потери данных: в Fluxs появился модуль миграции

4 000 контактов в Битрикс24. Сотни сделок. Задачи за несколько лет. И всё это нужно перенести в новую CRM? Раньше такой переезд мог занять несколько дней. А иногда именно из-за переноса данных компании годами оставались в неудобной системе. Мы решили эту проблему. 🚀 В Fluxs появился модуль «Миграция данных». Теперь можно переносить контакты, сделки, задачи, проекты и базу знаний из Битрикс24, amoCRM, Jira, Яндекс Трекера и Confluence. А если вашей системы нет в списке — загрузить данные через Excel, CSV или JSON.

fluxs.ru 27.10.2026 ▲ 1
Карьера

Чем Cal-WARN отличается от федерального WARN: разбор порогов, уведомлений и штрафов

Калифорния требует предупреждать о массовых сокращениях за 60 дней — как и федеральный закон США. Только порог ниже, парт-тайм считается, а правило одной трети не действует. Разбираем, что это меняет для работника и работодателя.

Слогер 27.10.2026 ▲ 0
Карьера

WARN Act: кто считается работодателем при массовом сокращении — разбор для дочек, холдингов и франшиз

По американскому закону уведомление за 60 дней обязан дать только работодатель. Разбираем, как регуляторы решают, какое юрлицо им является, и что делать, если в бумаге стоит незнакомая компания.

Слогер 27.10.2026 ▲ 0
Разработка

Разбор: почему профессию разработчика хоронят каждые 20 лет и как работать, когда всё меняется

Паника вокруг ИИ — не первая волна перемен в отрасли: похожие прогнозы звучали и в 1961 году, и в 2011-м. Разбираем, что в нынешней тревоге реально, а что стоит воспринимать как фоновый шум.

Слогер 27.10.2026 ▲ 0
Fluxs lenta
Реклама · fluxs.ru
Карьера

Страх провала и страх свидетелей: чем они отличаются и как начинать новое, когда страшно

«Я пока недостаточно хорош» — честная и бесполезная фраза. Обычно дело не в навыке, а в конкретных людях, которые увидят вашу первую попытку. Разбираем, как уменьшить ставку и всё-таки начать.

Слогер 26.10.2026 ▲ 0
Карьера

Почему удалёнка платит меньше — и в каких профессиях всё наоборот: разбор 8 049 вакансий

Медианная вилка на удалёнке ниже офисной на 6,5%. Но внутри функций разрыв меняет знак: безопасность и продажи получают премию за удалёнку, data/ML — самую большую скидку. Показываю, как читать эти цифры на переговорах.

Слогер 26.10.2026 ▲ 0
Личный опыт

Как заказать командную вышивку без минимальной партии: разбор и чек-лист

Малая команда редко может позволить себе стартовый тираж на 30 вещей. Разбираем схему, при которой вышивка делается по одной штуке — и при этом логотип у всех выглядит одинаково.

Слогер 26.10.2026 ▲ 0
Маркетинг

Автоматизация бизнеса за 15 000 или 170 000 рублей в год: в чём разница?

Автоматизация бизнеса за 15 000 или 170 000 ₽ в год. За что такая разница? 🤔 Представьте: компании нужно всего несколько вещей — принимать заявки, согласовывать счета и контролировать задачи сотрудников. А ей предлагают целую цифровую экосистему: CRM, телефонию, маркетинг, аналитику, десятки дополнительных функций… Но что, если половина из этого бизнесу вообще не нужна?

Слогер 25.10.2026 ▲ 1
Fluxs lenta
Реклама · fluxs.ru