В системе переводов исходный счёт у операции может отсутствовать, но если он указан, должны быть заданы обе части его составного ключа. Что именно гарантирует MATCH FULL в этой схеме?
CREATE TABLE account (
country_code CHAR(2) NOT NULL,
account_no VARCHAR(20) NOT NULL,
owner_name VARCHAR(100) NOT NULL,
PRIMARY KEY (country_code, account_no)
);
CREATE TABLE transfer (
transfer_id INTEGER PRIMARY KEY,
src_country CHAR(2),
src_account VARCHAR(20),
FOREIGN KEY (src_country, src_account)
REFERENCES account (country_code, account_no)
MATCH FULL
);
MATCH FULL требует, чтобы составная ссылка была либо полностью NULL, либо полностью заполненной и указывала на существующую строку родительской таблицы. Поэтому пара (NULL, '12345') будет отклонена, тогда как (NULL, NULL) допустима как отсутствие исходного счёта.
Без MATCH FULL обычно действует MATCH SIMPLE: наличие NULL хотя бы в одном столбце составного внешнего ключа может сделать проверку ссылочной целостности пройденной. Важно учитывать поддержку этого режима конкретной СУБД: MATCH FULL является стандартным SQL-механизмом, но реализован не во всех диалектах одинаково.
Внешний ключ первоначально решал проблему ссылочной целостности между связанными реляционными таблицами: дочерняя строка не должна ссылаться на несуществующую родительскую строку. Для составного ключа возникла дополнительная задача — определить смысл частично заполненной ссылки.
Режимы сопоставления MATCH SIMPLE и MATCH FULL задают разные правила обработки NULL в такой ссылке. Это позволяет отличить «связь отсутствует» от ошибочно переданной только части составного идентификатора.
В примере счёт определяется парой (country_code, account_no). Значение только src_account не идентифицирует счёт однозначно, потому что номер может иметь смысл только вместе с кодом страны.
Если СУБД использует обычную семантику MATCH SIMPLE, строка с (NULL, '12345') может пройти проверку внешнего ключа. В результате в базе появится частичная ссылка: приложение передало номер счёта, но база не потребовала код страны.
Такие данные затрудняют JOIN, отчёты и последующую обработку. Часть запросов будет трактовать строку как перевод без исходного счёта, а часть — как некорректно заполненную ссылку.
Для MATCH FULL действуют два допустимых состояния:
NULL;Следовательно, допустимы (NULL, NULL) и, например, ('RU', '12345'), если такая строка есть в account. Значения (NULL, '12345') и ('RU', NULL) нарушают целостность и должны быть отклонены.
MATCH FULL не отменяет проверку существования родительской строки. Если оба значения указаны, комбинация всё равно должна найтись в account.
Альтернативой может быть явное ограничение:
Но такой CHECK контролирует только совместную заполненность столбцов, а внешний ключ отдельно должен проверять существование пары. MATCH FULL выражает обе части намерения в одном ограничении, если СУБД поддерживает этот режим.
У внешнего ключа также могут быть дополнительные ограничения NOT NULL, но они подходят только тогда, когда ссылка обязательна. Если сделать оба столбца NOT NULL, исчезнет возможность представить перевод без исходного счёта.
В платёжной системе идентификатор счёта состоит из кода страны и локального номера. Для внутренних операций исходный счёт обязателен, но для входящих зачислений из внешних систем он иногда неизвестен.
Первый вариант — оставить MATCH SIMPLE. Его плюс — широкая поддержка и простая миграция. Минус — база принимает частично заполненные ссылки, поэтому ошибка интеграции обнаруживается только в приложении или отчётах.
Второй вариант — оставить обычный внешний ключ и добавить CHECK, проверяющий синхронность NULL. Это переносимее, но правило состоит из двух независимых ограничений, а разработчикам нужно явно поддерживать их вместе.
Третий вариант — использовать MATCH FULL. Команда выбрала его в СУБД, где режим поддерживался и был проверен тестами миграции. В результате отсутствие счёта стало представляться только парой NULL, а частичные идентификаторы отклонялись на уровне базы.
Что произойдёт, если оба столбца внешнего ключа имеют NULL при MATCH FULL?
Такая строка обычно считается отсутствующей ссылкой и допускается. MATCH FULL не означает, что составной внешний ключ всегда обязан быть заполнен; он запрещает именно частичное заполнение. Если ссылка обязательна, нужно дополнительно задать NOT NULL для обоих столбцов.
Проверяет ли MATCH FULL уникальность родительской комбинации?
Нет. Он определяет правила сопоставления NULL и проверки составной ссылки, но не заменяет уникальность родительского набора столбцов. Для корректного внешнего ключа родительская комбинация должна быть первичным ключом или поддерживаться подходящим уникальным ограничением, как в примере с PRIMARY KEY (country_code, account_no).
Можно ли заменить MATCH FULL двумя отдельными внешними ключами?
Обычно это меняет смысл модели. Два отдельных внешних ключа проверяют country_code и account_no независимо, а не существование конкретной пары. Даже если каждый компонент существует в родительской таблице, их комбинация может не существовать. Для составной идентичности нужен один составной внешний ключ, дополненный MATCH FULL или эквивалентным CHECK для совместной заполненности.