【问题标题】:Foreign key relationship issue外键关系问题
【发布时间】:2015-12-02 06:51:29
【问题描述】:

我有以下架构:

公司

id (PK)
name 

用户

id (PK)
name
dept_id (FK) // references id on Depts table

部门

id (PK)
name
manager_id (FK) // references id on Users table
company_id (FK) // references id on companies table

事实:

  • 一个部门属于一个公司
  • 一个部门由一个用户管理
  • 一个用户属于一个部门
  • 一个用户可以管理多个部门

当两个表都需要另一个表的外键时,如何创建用户记录或部门记录?

如果我有一个部门,它必须有一个经理,但如果我有一个用户,他们必须分配到一个部门。例如,如果我首先尝试创建用户,FK dept_id 约束将失败,因为不存在 Dept。如果我首先尝试创建Dept,FK manager_id 约束将失败,因为不存在用户。

***已更新****

如果引入了一些映射表,如果父表使用唯一的 ID 号作为主键,我为什么需要这么多复合主键和外键。

create table companies (
  id integer auto_inc primary key,
  company_name varchar(50) not null
);

create table departments (
  id integer auto_inc primary key
  company_id integer not null references id on companies
  dept_name varchar(50) not null
);

create table employees (
  id integer auto_inc primary key, 
  company_id integer not null references id on companies,
  emp_name varchar(50) not null,
  emp_num integer
);

create table managed_departments (
  company_id integer not null, 
  dept_id integer not null, 
  manager_id integer not null, 
  foreign key (company_id, dept_id) references departments,
  foreign key (manager_id) references employees (id),
  primary key (company_id, dept_id)
);

create table department_staff (
  company_id integer not null, 
  dept_id integer not null, 
  emp_id integer not null, 
  foreign key (company_id, dept_id) references managed_departments,
  foreign key (emp_id) references employees (id),
  primary key (company_id, dept_id, emp_id)
); 

* 更新 *

架构翻译成 Laravel

 Schema::create('companies', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name')->unique();
        $table->timestamps();
 });

 Schema::create('departments', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('company_id');
        $table->string('name');
        $table->timestamps();

        $table->foreign('company_id')->references('id')->on('companies');
        $table->unique(['company_id','name']);
}); 

Schema::create('employees', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('company_id');
        $table->string('name');
        $table->timestamps();

         $table->foreign('company_id')->references('id')->on('companies');
 });


  Schema::create('managed_departments', function (Blueprint $table) {
        $table->integer('company_id');
        $table->integer('department_id');
        $table->integer('manager_id');
        $table->timestamps();

        $table->primary(['company_id','department_id']);

        $table->foreign(['company_id','department_id'])
              ->references(['company_id','department_id'])
              ->on('departments');

        $table->foreign(['company_id','manager_id'])
              ->references(['company_id','id'])
              ->on('employees');
 });

 Schema::create('department_staff', function (Blueprint $table) {
        $table->integer('company_id');
        $table->integer('department_id');
        $table->integer('employee_id');
        $table->timestamps();

        $table->primary(['company_id','department_id', 'employee_id']);

        $table->foreign(['company_id','department_id'])
              ->references(['company_id','department_id'])
              ->on('managed_departments');

        $table->foreign(['company_id','employee_id'])
              ->references(['company_id','id'])
              ->on('employees');
 });

【问题讨论】:

  • 在手机上,所以只能发表评论,但您可以在 FK 列中允许 NULL 值(不是很好的选择)或将 FK 留在 Users 中并为 manager_id 创建映射表来自Depts
  • 根据您的 RDBMS,您可以使外键不是约束,因此不强制存在 FK。但是您可能希望添加自己的检查以确保引用完整性。
  • @JRLambert,映射表也不一定强制引用完整性,例如dept_managers (user_id, dept_id) 仍然意味着我必须自己检查。就像在其中一个表上创建一个可为空的 FK 一样容易,但是是哪一个?

标签: database-design foreign-keys entity-relationship


【解决方案1】:

当你发现自己像这样被打结时,你通常需要更多的桌子。

公司是最容易的部分。为了便于阅读,我几乎完全省略了 ID 号。

create table companies (
  company_name varchar(45) primary key
);

insert into companies values
('Vandelay, Inc'), ('Vertigenous, Inc');

将此处的“部门”视为组织结构图中的部门。这不是故事的全部。

create table departments (
  company_name varchar(45) not null references companies,
  dept_name varchar(30) not null,
  primary key (company_name, dept_name)
);

insert into departments values
('Vandelay, Inc', 'Human resources'), 
('Vertigenous, Inc', 'Personnel'),
('Vandelay, Inc', 'Information Technology'), 
('Vertigenous, Inc', 'Information Systems');

公司需要一些员工。员工有一个 ID 号,因为他们的名字在公司内不一定是唯一的。

create table employees (
  company_name varchar(45) not null references companies,
  emp_id integer not null,
  emp_name varchar(25) not null,
  primary key (company_name, emp_id)
);

insert into employees values
('Vandelay, Inc', 1, 'Steven McGuire'), 
('Vertigenous, Inc', 1, 'Michael McDonald'),
('Vandelay, Inc', 2, 'Rosalie Jimenez'),
('Vandelay, Inc', 3, 'Phil Roberson'),
('Vandelay, Inc', 4, 'Sylvester Davis');

现实世界中的部门(即与组织结构图上的部门相反)有一个经理。重叠外键约束保证经理和部门属于同一公司。

-- Assumes one current manager per department.
create table managed_departments (
  company_name varchar(45) not null,
  dept_name varchar(30) not null,
  foreign key (company_name, dept_name) references departments,
  manager_id integer not null,
  foreign key (company_name, manager_id) references employees (company_name, emp_id),
  primary key (company_name, dept_name)
);

insert into managed_departments values
('Vandelay, Inc', 'Human resources', 1),
('Vertigenous, Inc', 'Personnel', 1),
('Vandelay, Inc', 'Information Technology', 2);

要完成工作,请将员工分配到一个部门。同样,重叠的外键实现了一个业务需求——员工和被管理的部门属于同一家公司。

create table department_staff (
  company_name varchar(45) not null,
  dept_name varchar(30) not null,
  foreign key (company_name, dept_name) references managed_departments,
  emp_id integer not null,
  foreign key (company_name, emp_id) references employees,
  primary key (company_name, dept_name, emp_id)
);


insert into department_staff values
('Vandelay, Inc', 'Human resources', 1),
('Vandelay, Inc', 'Information Technology', 2),
('Vandelay, Inc', 'Information Technology', 3),
('Vandelay, Inc', 'Information Technology', 4);

这允许员工“在”多个部门,这在我的经验中实际上很常见。如果您希望每个员工只“在”一个部门,请在 DDL 中为“department_staff”添加一个约束。

unique (company_name, emp_id)

【讨论】:

  • 这是一个很好的解释。但是,更新您的答案以包含身份证号码实际上会更有意义。不过我有一个问题,为什么在自动递增或唯一 id 编号会达到相同效果时有这么多复合主键和外键 - 请参阅我更新的示例代码。
  • 另外,如果员工只在一个部门,那么您可以简单地在员工表中添加一个 dept_id 字段,然后完全摆脱 department_staff 表?
  • @ozzii:仔细看看。你的代码没有达到同样的效果。 a) 复合键没有错。 b) 对于公司,您的代码允许像 {1, Acme, Inc.}、{2, Acme, Inc.}、{3, Acme, Inc.} 这样的元组。 c) 外键引用必须引用具有某种唯一约束的列。 d) 代理键使每个 FK 约束等同于“更新级联”。这对于 every FK 约束来说是不明智的。 e) 我的代码是标准 SQL,在 PostgreSQL 中测试。您的代码不会在任何地方运行。
  • 我明白你的意思,但是当你使用 Laravel 构建 Web 应用程序时,eloquent 严重依赖代理键。另外据我所知,没有办法在 laravel 中定义复合唯一键,例如 unique (company_name, emp_id)
  • 你提到我的代码不会在任何地方运行,但我看不出它为什么不能在元组之外工作 - 请参阅模式转换为 Laravel。
猜你喜欢
  • 2017-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多