Oracle - частично обнуляемый составной внешний ключ - PullRequest
0 голосов
/ 13 сентября 2018

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

Таблица намного больше в плане столбцов, но я думаю, что мне удалось разобрать проблему в следующем примере:

create table quadrant (
  region number(9) not null,
  area number(9) not null,
  caption varchar2(20),
  primary key (region, area)
);

insert into quadrant (region, area, caption) values (10, 123, 'Chicago');
insert into quadrant (region, area, caption) values (10, 125, 'Wisconsin');

create table farm (
  id number(9),
  region_id number(9) not null,
  area_id number(9),
  name varchar2(50),
  constraint fk_region_area foreign key (region_id, area_id)
    references quadrant (region, area)
);

insert into farm (id, region_id, area_id, name) values (5, 10, null, 'farm 1');
insert into farm (id, region_id, area_id, name) values (6, 11, null, 'farm 2');

select * from farm;

Результат:

ID  REGION_ID  AREA_ID  NAME
--  ---------  -------  ------
5   10         <null>   farm 1  <-- Does it point to anything?
6   11         <null>   farm 2  <-- Region 11 doesn't even exist!

Если части внешнего ключа равны нулю, то какое это имеет значение?

  • Указывает ли REGION_ID, AREA_ID = (10, null) на что-либо или это просто бесполезная информация?
  • REGION_ID, AREA_ID = (11, null) указывает на что-нибудь?Я так не думаю.

Я испытываю желание добавить ограничение для принудительного применения ВСЕГО или НЕТ в терминах нулевых значений внешнего ключа.Имеет ли это смысл?

Но, прежде всего, каков вариант использования этой "функции"?

Ответы [ 5 ]

0 голосов
/ 14 сентября 2018

Ответ Мэтью показывает, как обойти это.Что касается того, почему это происходит, помните:

Ограничения отклоняют только те строки, где условие ложно.

Сравнение чего-либо с нулем => неизвестно.Таким образом, нулевые значения проходят ограничения, если вы специально не проверяете их.Что приводит к осиротевшим рядам, которые вы демонстрируете.

Как документы говорят:

Если какой-либо столбец составного внешнего ключа имеет значение NULL, то ненулевые части ключа не должнысоответствует любой соответствующей части родительского ключа

В большинстве случаев это ошибка или упущение со стороны оригинальных дизайнеров.

0 голосов
/ 14 сентября 2018

Спасибо за все ответы и комментарии.Эта проблема заставила меня узнать что-то новое, и это хорошо.@philipxy дал мне большую подсказку.Я хотел бы повторить то, что я узнал, так как это, вероятно, будет полезно кому-то еще, и это хорошее место, чтобы записать это.

Этот вопрос имеет две стороны: во-первых, какой частично нулевой внешний ключ означает , а во-вторых, как это реализовано .

Значение частично нулевых внешних ключей

Существует много споров о том, чтоэто значит - как указывает @ agiles231.NULL может означать:

  • значение неизвестно .
  • другие говорят, что это означает, что неверно .
  • другие говорят, что NULL - это истинное значение как таковое.

Короче говоря, пока нет однозначного ответа на его значение.

Я полагаю, в зависимости от того, как люди интерпретируют нулевые значения, затем стратегию их использования во внешних ключах (идля проверки их) может быть другим.

Реализация частично нулевых внешних ключей

Стандарт SQL-92 определяет (раздел 4.10.2)три различных способа сопоставления составных внешних ключей со значениями, допускающими значение NULL:

  • Match SIMPLE : если какой-либо столбец составного внешнего ключа имеет значение NULL, тогда внешний ключ принимается, сохранено, но не проверено по указанной таблице.Обычно это предложение баз данных в режиме по умолчанию.В стандарте SQL-92 этот режим описан, но не назван.

  • Match PARTIAL : если какой-либо столбец составного внешнего ключа имеет значение null, то каждый неСтолбец NULL сопоставляется с указанной таблицей, чтобы проверить наличие хотя бы одной строки, в которой присутствует значение.Я не видел ни одной базы данных, реализующей этот режим.

  • Match FULL : частично нулевые внешние ключи не принимаются.Либо внешний ключ является полностью нулевым, либо полностью не нулевым.Когда ноль, нет проверки по ссылочной таблице.Когда не ноль, это полностью проверено относительно ссылочной таблицы.Это то, что я ожидал как поведение по умолчанию (в моем блаженном невежестве).

Хорошо, я проверил, как 10 различных баз данных реализовали эти режимы, и вот что я нашел:

Database Engine  Match SIMPLE  Match PARTIAL  Match FULL
---------------  ------------  -------------  ----------
Oracle 12c1      YES*1         NO             NO
DB2 10.5         YES*1         NO             NO
PostgreSQL 10    YES*1         NO             YES
SQL Server 2014  YES*1         NO             NO
MariaDB 10.3     YES*1         NO*2           NO*2
MySQL 8.0        YES*1         NO*2           NO*2
Sybase ASE 16    YES*1         NO             YES
H2 1.4           YES*1         NO             NO
Derby 10.13      YES*1         NO             NO
HyperSQL 2.3     YES*1         NO             YES

* 1 Это режим по умолчанию.

* 2 Принимается при создании таблицы, но игнорируется.

Короче:

  • Все протестированные базы данных по умолчанию ведут себя одинаково: по умолчанию они соответствуют SIMPLE.

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

  • PostgreSQL реализует Match FULL, а также Sybase ASE.Это прекрасные новости!Удивительно, но HyperSQL (эта крошечная база данных) тоже делает это.

Обходной путь для реализации Match FULL

Хорошая новость заключается в том, что существует довольно простой обходной путь для реализацииПодберите FULL, если вам это нужно, в любой из протестированных баз данных.Просто добавьте ограничение таблицы, которое допускает либо все пустые столбцы, либо все ненулевые.Что-то вроде:

create table farm (
  id int,
  region_id int,
  area_id int,
  name varchar(50),
  constraint fk_region_area foreign key (region_id, area_id)
    references quadrant (region, area),
  constraint fkfull_region_area check ( -- here's the workaround
    region_id is null and area_id is null or
    region_id is not null and area_id is not null)
);

insert into farm (id, region_id, area_id, name) values (5, 10, null, 'farm 1'); -- fails

insert into farm (id, region_id, area_id, name) values (6, 11, null, 'farm 2'); -- fails

insert into farm (id, region_id, area_id, name) values (7, 10, 125, 'farm 3'); -- succeeds

insert into farm (id, region_id, area_id, name) values (8, null, null, 'farm 4'); -- succeeds

Работает довольно аккуратно.

Наконец, и, как очень личное мнение, я бы ожидал, что Match FULL будет стратегией сопоставления по умолчанию.Может быть, просто для меня использование (по умолчанию) внешних ключей, которые не указывают на другие строки, вызывает ошибки в приложениях, использующих базу данных.

Я думаю, что большинство разработчиков поймут ПОЛНОСТЬЮ легко, по сравнению с ПРОСТОЙ.И ЧАСТИЧНЫЙ способ намного сложнее и потенциально подвержен ошибкам.Просто мое мнение.

0 голосов
/ 13 сентября 2018

Некоторые предположения, относящиеся к вашей «особенности»: Может быть, поле области относится только к некоторым фермам?Пример: ферма с указанным районом должна платить некоторую наценку или налог (угадайте здесь, так как я не знаю ваших данных)?В этом случае NULL означает что-то (не требуется платить).Может быть, есть фермы, которые существовали до внедрения «области», и, таким образом, никогда не были назначены один?В этом случае NULL действительно означает NULL, так как область никогда не существовала, поэтому неизвестна.

0 голосов
/ 13 сентября 2018

Я не знаю, имеет ли это смысл для вашей модели данных, но есть определенные варианты использования для частично пустых внешних ключей.

Рассмотрим простую таблицу основных средств (компьютеры, автомобили, здания и т. Д. - вещи, которые бухгалтеры будут амортизировать). Предположим, что они хотят знать, где актив используется, поэтому у них есть два столбца: company_id и department_id.

Некоторые активы, такие как здания, могут быть разделены между департаментами, поэтому я ожидаю, что внешний ключ, такой как (123, null). Я также ожидал бы, что отдельный внешний ключ будет равен только таблице COMPANY в COMPANY_ID.

Смысл такой установки заключается в том, что company_id должен быть известным значением, а комбинация компании / отдела, , если она существует , должна быть известной комбинацией.

UPDATE

Я не уверен, почему вы думаете, что Oracle не может делать то, что я описываю. Вот простой тест:

CREATE TABLE tst_company 
  ( company_id NUMBER NOT NULL PRIMARY KEY );

CREATE TABLE tst_department
  ( company_id NUMBER NOT NULL,
    department_id NUMBER NOT NULL,
    CONSTRAINT tst_department_pk PRIMARY KEY ( company_id, department_id ),
    CONSTRAINT tst_department_f1 FOREIGN KEY ( company_id ) REFERENCES tst_company ( company_id ) );

CREATE TABLE tst_asset
  ( asset_id NUMBER NOT NULL PRIMARY KEY,
    company_id NUMBER NOT NULL,
    department_id NUMBER,
    CONSTRAINT tst_asset_f1 FOREIGN KEY ( company_id ) REFERENCES tst_company ( company_id ),
    CONSTRAINT tst_asset_f2 FOREIGN KEY ( company_id, department_id ) REFERENCES tst_department ( company_id, department_id ) );

INSERT INTO tst_company ( company_id ) VALUES (1);
INSERT INTO tst_department ( company_id, department_id ) VALUES (1, 10);
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1001, 1, 10);  -- Department specific asset
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1002, 1, NULL);  -- Non-department specific asset

INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1003, 2, NULL);  -- Bad company - fails
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1004, 1, 11);  -- Bad department - fails
INSERT INTO tst_asset ( asset_id, company_id, department_id ) VALUES (1005, 2, 11);  -- Bad company AND department - fails
0 голосов
/ 13 сентября 2018

Существует много споров об использовании нулей, чтобы что-то значить.Некоторые утверждают, что нулевое значение означает, что значение неизвестно или что оно представляет недействительность, другие утверждают, что оно само является действительным значением.Я подозреваю, что в этом случае, это неизвестно.Предположим, что вы документировали местонахождение ферм в округе за 100 лет до этого времени.Используя некоторые учебники по местной истории, вы наметили 70% существующих ферм того периода и их точные границы (или около того), но для оставшихся 30% некоторые имеют известные регионы, а некоторые, как известно, существуют.В этом случае я бы определенно сказал, что нулевой внешний ключ имеет смысл.Это просто неизвестная информация.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...