Как правильно связать таблицу с двумя другими таблицами, когда любая из таблиц может быть правильным вариантом? - PullRequest
0 голосов
/ 24 января 2012

Введение

У меня есть три таблицы «Клиенты, физические лица и компании», и клиент может быть физическим лицом или компанией, но не обоими. Я хочу узнать общее мнение о том, как правильно связать эти три таблицы.

Подробности с примером

Три таблицы - это «клиенты», «компании» и «частные лица», а код MySQL для генерации базовых таблиц:

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
USE `mydb` ;

-- -----------------------------------------------------
-- Table `mydb`.`customers`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`customers` (
  `CustomerID` INT NOT NULL AUTO_INCREMENT ,
  `Name` VARCHAR(45) NULL ,
  PRIMARY KEY (`CustomerID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`individuals`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`individuals` (
  `IndividualID` INT NOT NULL AUTO_INCREMENT ,
  `First Name` VARCHAR(45) NULL ,
  `Last Name` VARCHAR(45) NULL ,
  `DOB` DATE NULL ,
  PRIMARY KEY (`IndividualID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`companies`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`companies` (
  `CompanyID` INT NOT NULL AUTO_INCREMENT ,
  `Name` VARCHAR(60) NULL ,
  `StartedDate` DATE NULL ,
  `Address` VARCHAR(500) NULL ,
  PRIMARY KEY (`CompanyID`) )
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Это базовые таблицы без ссылок между ними. Я играл с несколькими методами связывания таблиц, но ни один из них не чувствовал себя правильным. Первый метод состоял в том, чтобы в основном опубликовать «IndividualID» и «CompanyID» в таблице клиентов и логическое значение, чтобы сказать, какой это был, но это оставляло его открытым, чтобы потенциально заполнить и не было возможности подкрепить его БД напрямую Кроме того, он просто не чувствовал себя правильно, что было так:

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
USE `mydb` ;

-- -----------------------------------------------------
-- Table `mydb`.`companies`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`companies` (
  `CompanyID` INT NOT NULL AUTO_INCREMENT ,
  `Name` VARCHAR(60) NULL ,
  `StartedDate` DATE NULL ,
  `Address` VARCHAR(500) NULL ,
  PRIMARY KEY (`CompanyID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`individuals`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`individuals` (
  `IndividualID` INT NOT NULL AUTO_INCREMENT ,
  `First Name` VARCHAR(45) NULL ,
  `Last Name` VARCHAR(45) NULL ,
  `DOB` DATE NULL ,
  PRIMARY KEY (`IndividualID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`customers`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`customers` (
  `CustomerID` INT NOT NULL AUTO_INCREMENT ,
  `Name` VARCHAR(45) NULL ,
  `bIsCompany` TINYINT(1) NOT NULL ,
  `IndividualID` INT NULL ,
  `CompanyID` INT NULL ,
  PRIMARY KEY (`CustomerID`) ,
  INDEX `Customer_Company` (`CompanyID` ASC) ,
  INDEX `Customer_Individual` (`IndividualID` ASC) ,
  CONSTRAINT `Customer_Company`
    FOREIGN KEY (`CompanyID` )
    REFERENCES `mydb`.`companies` (`CompanyID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `Customer_Individual`
    FOREIGN KEY (`IndividualID` )
    REFERENCES `mydb`.`individuals` (`IndividualID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Другой метод состоял в том, чтобы добавить две таблицы между клиентом и двумя другими таблицами, которые связывали их, и это было лучше, но не идеально, так как опять вы МОЖЕТЕ иметь связь в обеих. Это выглядело так:

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
USE `mydb` ;

-- -----------------------------------------------------
-- Table `mydb`.`customers`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`customers` (
  `CustomerID` INT NOT NULL AUTO_INCREMENT ,
  `Name` VARCHAR(45) NULL ,
  PRIMARY KEY (`CustomerID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`individuals`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`individuals` (
  `IndividualID` INT NOT NULL AUTO_INCREMENT ,
  `First Name` VARCHAR(45) NULL ,
  `Last Name` VARCHAR(45) NULL ,
  `DOB` DATE NULL ,
  PRIMARY KEY (`IndividualID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`companies`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`companies` (
  `CompanyID` INT NOT NULL AUTO_INCREMENT ,
  `Name` VARCHAR(60) NULL ,
  `StartedDate` DATE NULL ,
  `Address` VARCHAR(500) NULL ,
  PRIMARY KEY (`CompanyID`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`company_customer`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`company_customer` (
  `CustomerID` INT NOT NULL ,
  `CompanyID` INT NOT NULL ,
  PRIMARY KEY (`CustomerID`, `CompanyID`) ,
  INDEX `CompanyCustomer_CompanyID` (`CompanyID` ASC) ,
  INDEX `CompanyCustomer_CustomerID` (`CustomerID` ASC) ,
  CONSTRAINT `CompanyCustomer_CompanyID`
    FOREIGN KEY (`CompanyID` )
    REFERENCES `mydb`.`companies` (`CompanyID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `CompanyCustomer_CustomerID`
    FOREIGN KEY (`CustomerID` )
    REFERENCES `mydb`.`customers` (`CustomerID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `mydb`.`individual_customer`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `mydb`.`individual_customer` (
  `IndividualID` INT NOT NULL ,
  `CompanyID` INT NOT NULL ,
  PRIMARY KEY (`IndividualID`, `CompanyID`) ,
  INDEX `CompanyCustomer_CompanyID` (`IndividualID` ASC) ,
  INDEX `CompanyCustomer_CustomerID` (`IndividualID` ASC) ,
  CONSTRAINT `IndividualCustomer_CompanyID0`
    FOREIGN KEY (`IndividualID` )
    REFERENCES `mydb`.`individuals` (`IndividualID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `IndividualCustomer_CustomerID0`
    FOREIGN KEY (`IndividualID` )
    REFERENCES `mydb`.`customers` (`CustomerID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Как я уже говорил, последний вариант был тем методом, который я предпочел, но он все еще не был корректным и мог привести к проблемам. SO:

Вопросы:

  • Есть ли другие варианты
  • Каково общее мнение по поводу указанных вариантов, и я упустил какие-либо преимущества / недостатки этих вариантов.

Заранее спасибо.

Ответы [ 3 ]

2 голосов
/ 24 января 2012

Лично я пойду на что-то, основанное на принципах ОО.

Заказчик: будет суперклассом.И в нем будут все элементы, которые являются общими как для компаний, так и для частных лиц.То есть Id, Name (и, возможно, главный адрес или идентификатор таблицы основных адресов и все остальное, что является общим для любого клиента) плюс поле «Тип» (например, «IND», «COM»).

Физические лица: имеет внешний ключ для Клиента и все поля, относящиеся к этому Физическому лицу, такие как Отчество, DOB и т. Д. Внешний ключ также уникален.

Компании: То же - иностранныеключ к полям клиента и компании.Внешний ключ также является уникальным.

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

Пример:

    Select * from Individuals where exists 
    (select 'x" from Customers 
                where customer-id=individual-id and type="IND").

------------------ ОБНОВЛЕНИЕ:

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

Select Individuals.id, 
       Individuals.field1, 
       Individuals.field2, 
       Customer.FieldX, Customer.FieldY 
 from Individuals ind, Customers Cus 
 where ind.id=cus.id and cus.type="IND" 

- это способ "восстановить" отдельного клиента, используя поля из обеих таблиц.Это позволяет вам получить доступ к нему в целом, по крайней мере, при его чтении.

Я не уверен, в чем проблема при разделении полей на две отдельные таблицы, на самом деле.

0 голосов
/ 26 января 2012

Вам действительно нужны промежуточные таблицы "Individual_customer" и "company_customer"?Являются ли они действительно отношениями, означающими, что клиент может быть связан с несколькими компаниями или частными лицами (и наоборот, с несколькими клиентами)?Или это просто 1: 1?

Если это 1: 1, вы можете удалить промежуточные таблицы и добавить столбец CustomerID к таблицам "индивидуумы" и "компании".Это решило бы вашу проблему «невозможно переместить общие данные в таблицу клиентов».И добавьте CustomerID к первичному ключу или новому уникальному ключу, чтобы предотвратить повторяющиеся записи в этих таблицах.

Возможность того, что клиент окажется как компанией, так и частным лицом, все еще остается, но это проблема, которую вы ''Мы не собираемся решать (легко), если вы не измените таблицы, как предложено p.marino и Damir.Вы не можете применить это требование в mySQL с помощью простого первичного или уникального ключа в базе данных, если данные хранятся в нескольких таблицах.Это может сделать только логика приложения или сохраненная функция / процедура.

0 голосов
/ 24 января 2012

Поместите все общие поля в таблицу Customer. Таблицы Company и Person имеют только столбцы, специфичные для каждой из них. Customer.CustomerType является дискриминатором.

enter image description here

Взгляните на некоторые похожие вопросы тоже.

...