Использование идентификаторов из нескольких таблиц в одном столбце - PullRequest
4 голосов
/ 03 августа 2011

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

Системные правила следующие:

  1. Отделы могут иметь от 0 до многих отделов.
  2. Отдел должен принадлежать только одному отделу.
  3. Статья может быть назначена либо отделу, либо отделу этого отдела.

Схема:

Department
---------- 
DepartmentID (PK) int NOT NULL
DepartmentName varchar(50) NOT NULL

Division
--------
DivisionID (PK) int NOT NULL
DepartmentID (FK) int NOT NULL
DivisonName varchar(50) NOT NULL

Article
-------
ArticleID (PK) int NOT NULL
UniqueID int NOT NULL
ArticleName varchar(50) NOT NULL

Он определил схему, используя мнимое правило (из-за отсутствия лучшего термина), что все DepartmentID будут между 1 и 100, а все DivisionID будут между 101 и 200. Он утверждает, что при запросе таблицы Article, вы будете знать, является ли UniqueID из таблицы Department или таблицы Division в зависимости от того, в какой диапазон он попадает.

Я думаю, что это плохой дизайн, и предложил следующую альтернативную схему:

Department
----------
DepartmentID (PK) int NOT NULL
ParentDepartmentID (FK) int NULL /* Self-referencing foreign key.  Divisions have parent departments. */
DepartmentName varchar(50) NOT NULL

Article
-------
ArticleID (PK) int NOT NULL
DepartmentID (FK) int NOT NULL
ArticleName varchar(50) NOT NULL

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

Мой конкретный вопрос таков:

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

Ответы [ 5 ]

4 голосов
/ 03 августа 2011

Ваш коллега реализовал проект под названием Полиморфные ассоциации . То есть «внешний ключ» относится к одной из двух разных родительских таблиц. Большинство людей добавляют еще один столбец parent_type или что-то в этом роде, чтобы вы могли определить, к какой родительской таблице относится данная строка. В случае вашего коллеги он вместо этого разделил диапазон идентификаторов. Это хрупкий дизайн, потому что вы не можете применить его на уровне базы данных. Если вы когда-нибудь введете номер отдела> 100, вы не сможете узнать, относятся ли ваши статьи к отделу или отделу.

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

Вот еще одна альтернатива:

Подумайте об объектно-ориентированном дизайне. Если вы хотите, чтобы два разных класса имели статьи, вы можете создать общий суперкласс или общий интерфейс для этих двух классов. Вы можете сделать то же самое в SQL:

ArticleProducer
---------------
ProducerID (PK) int NOT NULL

Department
----------
DepartmentID (PK) int NOT NULL, (FK)->ArticleProducer
DepartmentName varchar(50) NOT NULL

Division
--------
DivisionID (PK) int NOT NULL, (FK)->ArticleProducer
DepartmentID (FK) int NOT NULL
DivisonName varchar(50) NOT NULL

Article
-------
ArticleID (PK) int NOT NULL, (FK)->ArticleProducer
UniqueID int NOT NULL
ArticleName varchar(50) NOT NULL

Таким образом, статья должна быть произведена одним ArticleProducer. Каждый департамент или отдел является производителем товара.

См. Также Почему вы не можете иметь внешний ключ в полиморфной ассоциации?

Подробнее о полиморфных ассоциациях см. Мою презентацию Практические объектно-ориентированные модели в SQL или мою книгу Антипаттерны SQL: предотвращение ловушек программирования баз данных .


Комментарии от Эрвина Смута:

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

CREATE TABLE ArticleProducerTypes (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO ArticleProducerTypes VALUES (1), (2);

CREATE TABLE ArticleProducer (
  ProducerID INT UNSIGNED NOT NULL PRIMARY KEY,
  ProducerType TINYINT UNSIGNED NOT NULL,
  UNIQUE KEY (ProducerID,ProducerType),
  FOREIGN KEY (ProducerType)
    REFERENCES ArticleProducerTypes(ProducerType)
) ENGINE=InnoDB;

CREATE TABLE DepartmentProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO DepartmentProducerType VALUES (1);

CREATE TABLE Department (
  DepartmentID INT UNSIGNED NOT NULL PRIMARY KEY,
  DepartmentName VARCHAR(50) NOT NULL,
  ProducerType TINYINT UNSIGNED NOT NULL,
  FOREIGN KEY (DepartmentID, ProducerType) 
    REFERENCES ArticleProducer(ProducerID, ProducerType),
  FOREIGN KEY (ProducerType)
    REFERENCES DepartmentProducerType(ProducerType) -- restricted to '1'
) ENGINE=InnODB;

CREATE TABLE DivisionProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO DivisionProducerType VALUES (2);

CREATE TABLE Division (
  DivisionID INT UNSIGNED NOT NULL PRIMARY KEY,
  ProducerType TINYINT UNSIGNED NOT NULL,
  DepartmentID INT UNSIGNED NOT NULL,
  FOREIGN KEY (DivisionID, ProducerType) 
    REFERENCES ArticleProducer(ProducerID, ProducerType),
  FOREIGN KEY (ProducerType)
    REFERENCES DivisionProducerType(ProducerType), -- restricted to '2'
  FOREIGN KEY (DepartmentID)
    REFERENCES Department(DepartmentID)  
) ENGINE=InnODB;

CREATE TABLE Article (
  ArticleID INT UNSIGNED NOT NULL PRIMARY KEY,
  ArticleName VARCHAR(50) NOT NULL,
  FOREIGN KEY (ArticleID)
    REFERENCES ArticleProducer(ProducerID)
);

Теперь на каждую данную строку в ArticleProducer могут ссылаться либо отдел, либо отдел, но не оба.

Если мы хотим добавить новый тип производителя, мы добавляем одну строку в таблицу поиска ArticleProducerTypes и создаем пару новых таблиц для нового типа. Например:

INSERT INTO ArticleProducerTypes VALUES (3);

CREATE TABLE PartnerProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO PartnerProducerType VALUES (3);

CREATE TABLE Partner (
  PartnerID INT UNSIGNED NOT NULL PRIMARY KEY,
  ProducerType TINYINT UNSIGNED NOT NULL,
  FOREIGN KEY (PartnerID, ProducerType) 
    REFERENCES ArticleProducer(ProducerID, ProducerType),
  FOREIGN KEY (ProducerType)
    REFERENCES PartnerProducerType(ProducerType) -- restricted to '3'
) ENGINE=InnODB;

Но у нас все еще есть возможность, что ни не содержит ссылку на данную строку в ArticleProducer; то есть мы не можем создать ограничение, которое вынуждает создать строку в одной из зависимых таблиц. У меня нет решения для этого.

2 голосов
/ 03 августа 2011

1NF

Каждое пересечение строк и столбцов содержит ровно одно значение из применимый домен (и ничего больше).

http://en.wikipedia.org/wiki/First_normal_form#1NF_tables_as_representations_of_relations

Самый простой способ решить вашу проблему - ввести разделение «по умолчанию» для каждого отдела, что просто означает «весь отдел». После этого просто свяжите все статьи с разделами. Может быть, что-то вроде этого (DepartmentDivisionNo = 0 означает целый отдел):

enter image description here

1 голос
/ 22 октября 2016

Мне действительно нравится ответ Дамира - он «переосмысливает» вопрос и дает правильный ответ на этот новый вопрос. Однако существует разница между отделом и отделом - предположительно, каждое подразделение может иметь доступ к статьям, принадлежащим их отделу. Наличие статей, относящихся к подразделению по умолчанию или целому отделу отдела, означает, что существует два различных типа подразделений. Отныне вы будете делать запросы, такие как

select * from xxx x inner join division d where d.joinkey = x.joinkey and d.division != 0.

Вместо этого я называю свое решение "не экономьте на отношениях":

Department
---------- 
DepartmentID (PK) int NOT NULL
DepartmentName varchar(50) NOT NULL

Division
--------
DivisionID (PK) int NOT NULL
DepartmentID (FK) int NOT NULL
DivisonName varchar(50) NOT NULL

Article
-------
ArticleID (PK) int NOT NULL
ArticleName varchar(50) NOT NULL

ArticleBelongsToDepartment
--------------------------
ArticleID (PK) (FK) int NOT NULL
DepartmentID (FK) int NOT NULL

ArticleBelongsToDivision
--------------------------
ArticleID (PK) (FK) int NOT NULL
DivisionID (FK) int NOT NULL

Теперь, как обеспечить соблюдение некоторых ограничений, которые были введены? Чтобы решить эту проблему, вы можете создать «корзину для статей», где каждая статья должна принадлежать к корзине, а для отделов и отделов есть ячейки.

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

0 голосов
/ 03 августа 2011

re: Он определил схему, используя воображаемое правило (из-за отсутствия лучшего термина), что все DepartmentID будут между 1 и 100, а все DivisionID будут между 101 и 200.

Если это то, что он хочет сделать, ему следует использовать другое поле, например isDepartment да / нет.Тогда у него будет одна таблица для отделов и отделов с ID, именем и isDepartment, а поле идентификатора будет FK в таблице Article.

Это разрешит перекрывающиеся идентификаторы отделов и отделов, но не 1отношения между департаментами и отделами.Для обеспечения этой связи вам понадобятся две таблицы.

Вы также можете ввести поле AuthorID как в таблицы отделов, так и в таблицах отделов, которые имеют отношение FK с Article.Это может быть автоматически сгенерированное поле.Это способ нормализовать составной ключ в таблице деления.

0 голосов
/ 03 августа 2011

Отделы и отделы должны храниться в одной таблице.Вот так:

DepDiv
---------- 
ID (PK) int NOT NULL
Name varchar(50) NOT NULL
Type int -- ex.: 1 for department, 2 for division, etc., incase you need to differentiate later

Это такие схожие элементы - вы должны относиться к ним одинаково.

После этого вам не понадобится никакая винтовая логика: диапазоны для идентификационных номеров.В любом случае, такой подход слишком масштабный.

Удачи.

...