【问题标题】:Can a foreign key refer to a primary key in the same table?外键可以引用同一张表中的主键吗?
【发布时间】:2013-09-11 21:44:36
【问题描述】:

我只是认为答案是错误的,因为外键没有uniqueness 属性。

但是有人说可以在自己加入表的情况下。 我是SQL 的新手。如果它是真的,请解释如何以及为什么?

Employee table
| e_id | e_name  | e_sala  |  d_id  |
|----  |-------  |-----    |--------|
|  1   |   Tom   |  50K    |    A   |
|  2   | Billy   |  15K    |    A   |
|  3   | Bucky   |  15K    |    B   |


department table
| d_id | d_name  |
|----  |-------  |
|  A   |   XXX   | 
|  B   |   YYY   | 

现在,d_id 是外键,所以它怎么可能是主键。并解释一下join。它有什么用?

【问题讨论】:

  • 您可能想要添加您正在使用的 DBMS,以便获得适用于您的 DBMS 的示例。

标签: sql foreign-keys primary-key


【解决方案1】:

我认为这个问题有点令人困惑。

如果您的意思是“外键可以'引用'同一张表中的主键吗?”,答案是肯定的,正如有些人回答的那样。例如,在员工表中,员工的一行可能有一个用于存储经理的员工编号的列,其中经理也是员工,因此表中的一行与任何其他员工的行一样。

如果您的意思是“列(或列集)可以是同一个表中的主键和外键吗?”,在我看来,答案是否定的;似乎毫无意义。但是,下面的定义在 SQL Server 中成功了!

create table t1(c1 int not null primary key foreign key references t1(c1))

但我认为除非有人想出一个实际的例子,否则有这样的限制是没有意义的。

AmanS,在您的示例中,d_id 在任何情况下都不能是 Employee 表中的主键。一张表只能有一个主键。我希望这能消除你的疑问。 d_id 是/只能是部门表中的主键。

【讨论】:

  • 我很清楚 d_id 是部门表的主键。员工表中的 d_id 也是外键。而且您还提到 d_id 可以是同一张表(员工)中的主键。我的老师告诉我,如果员工表自我加入,这是可能的。这点我怎么不清楚?
  • @AmanS,您说“您提到 d_id 可以是同一张表(员工)中的主键”。但是我没有;这是我的声明“AmanS,在您的示例中,d_id 在任何情况下都不能成为 Employee 表中的主键”。希望你能明白。即使在自连接的情况下也是不可能的。表中的另一列可以引用同一张表的主键。例如。创建表员工(e_id int 主键,e_name varchar(30),e_mgr int,外键 (e_mgr) 引用员工(e_id))。这是一个自连接案例,e_mgr 是一个外键,它引用主键 e_id。
  • 你能告诉我任何关于外国可以引用主键的查询的实际例子吗?但是下面的人(ryvantage)也说可以....我很困惑,请帮忙
  • @AmanS,我在之前的评论中已经给出了一个员工表的例子,ryvantage 也给出了一个带有数据的人表的例子。这些都是实际的例子。你读过任何关于数据库系统的书,再读一遍这些 cmets,我希望你能明白。没有什么比引用查询(DML 语句)中的主键的外键更好的了。该引用仅出现在 DDL 语句 CREATE TABLE 或 ALTER TABLE 语句中。而我的 DDL 语句 CREATE TABLE employee... 在我之前的评论中显示了它。
  • @AmanS,这可能是您正在寻找的。选择语句“SELECT DISTINCT e.e_id AS 'Manager Id', e.e_name AS 'Manager Name' FROM employee e, employee m WHERE e.e_id = m.e_mgr;”为您提供所有作为经理的员工。这里将主键列 e_id 与外键列 e_mgr 进行比较。
【解决方案2】:

这可能是一个很好的解释示例

CREATE TABLE employees (
id INTEGER NOT NULL PRIMARY KEY,
managerId INTEGER REFERENCES employees(id), 
name VARCHAR(30) NOT NULL
);

INSERT INTO employees(id, managerId, name) VALUES(1, NULL, 'John');
INSERT INTO employees(id, managerId, name) VALUES(2, 1, 'Mike');

-- 解释: ——在这个例子中。 -- John 是 Mike 的经理。迈克不管理任何人。 -- Mike 是唯一一个不管理任何人的员工。

【讨论】:

    【解决方案3】:

    当然,为什么不呢?假设您有一个Person 表,其中包含idnameageparent_id,其中parent_id 是同一个表的外键。您不需要将 Person 表规范化为 ParentChild 表,这将是矫枉过正。

    Person
    | id |  name | age | parent_id |
    |----|-------|-----|-----------|
    |  1 |   Tom |  50 |      null |
    |  2 | Billy |  15 |         1 |
    

    类似的东西。

    我想为了保持一致性,parent_id 至少需要 1 个空值。一个“阿尔法男性”行。

    编辑:正如 cmets 所示,Sam 找到了不这样做的充分理由。似乎在 MySQL 中,当您尝试对主键进行编辑时,即使您指定 CASCADE ON UPDATE,它也不会正确传播编辑。尽管主键(通常)在生产环境中禁止编辑,但它仍然是一个不容忽视的限制。因此,我将答案更改为:- 您可能应该避免这种做法,除非您对生产系统有非常严格的控制(并且可以保证没有人会实施编辑 PK 的控制)。我没有在 MySQL 之外测试过。

    【讨论】:

    • 只是看看我编辑的问题的例子。你的答案仍然是肯定的吗?
    • 它不适用于update。检查sqlfiddle.com/#!9/445052/1/0 然后尝试添加Update menus set id = 6 WHERE id = 1; 你会得到#1451 - Cannot delete or update a parent row
    • @Sam 两件事:1)我看到它不起作用,但我不同意它应该失败。通过级联更新,更新失败似乎没有任何合乎逻辑的原因。 2) 谁在实际情况下改变PK?自找麻烦的人哈哈
    • @ryvantage 完全同意你在实际情况下不更新PK。只是想添加此评论以提及这是 mysql 中已知的 documented 限制(或 bug )。
    • 我看到编辑中提到了ON UPDATE CASCADE。我建议不要使用[ON UPDATE CASCADE] [ON DELETE CASCADE] 作为级联操作doesn't trigger triggers。如果您有应在表 A 中的更新时触发的触发器,并且您在表 B 中执行更新导致 A 中的级联更改,则不会触发表 A 上的触发器。请改用ON UPDATE RESTRICT,并在应用程序逻辑中手动处理必要的删除。
    【解决方案4】:

    例如:类别的n个子类别级别。下表主键id由外键引用sub_category_id

    【讨论】:

      【解决方案5】:

      在同一个表中使用其他行的 id 作为外键的一个很好的例子是嵌套列表。

      删除包含子代(即引用父 ID 的行)和子代(即引用子代 ID)的行将删除一系列行。

      这将省去很多麻烦(以及很多关于如何处理孤儿的代码 - 即引用不存在的 id 的行)。

      【讨论】:

        【解决方案6】:

        其他答案已经给出了足够清楚的例子,说明一条记录引用了同一表中的另一个记录。

        对于在同一个表中引用自身的记录,甚至还有有效的用例。例如,一个接受许多投标的销售点系统可能需要知道当付款不是销售的确切价值时使用哪个投标来进行更改。对于许多相同的标书,对于其他是国内现金的标书,对于其他标书,不允许进行任何形式的更改。

        所有这一切都可以用一个单一的属性非常优雅地表示,该属性是一个引用同一表的主键的外键,其值有时与同一记录的相应主键匹配。在此示例中,可能需要缺少值(也称为 NULL 值)来表示不相关的含义:此标书只能以其完整值使用。

        流行的关系数据库管理系统顺利支持此用例。

        要点:

        1. 当插入一条记录时,外键引用被验证是否存在于插入之后,而不是之前插入。

        2. 当使用单个语句插入多条记录时,插入记录的顺序很重要。分别检查每条记录的约束。

        3. 某些其他数据模式,例如那些涉及通过两个或多个表的记录级别的循环依赖的数据模式,根本不能完全插入,或者至少不能在启用所有外键的情况下,它们必须使用插入和更新的组合(如果确实需要)。

        【讨论】:

          【解决方案7】:

          添加到@mysagar 的答案,下面演示了在 MySQL 中执行相同操作的方法 -

          CREATE TABLE t1 (
              -> c1 INT NOT NULL,
              -> PRIMARY KEY (c1),
              -> CONSTRAINT fk FOREIGN KEY (c1)
              -> REFERENCES t1 (c1)
              -> ON UPDATE RESTRICT
              -> ON DELETE RESTRICT
              -> );
          

          会报错-

          ERROR 1822 (HY000): Failed to add the foreign key constraint. Missing index for constraint 'fk' in the referenced table 't1'
          

          正确的做法是——

          CREATE TABLE t1 (
              -> c1 INT NOT NULL,
              -> PRIMARY KEY (c1),
              -> KEY i (c1),
              -> CONSTRAINT fk FOREIGN KEY (c1)
              -> REFERENCES t1 (c1)
              -> ON UPDATE RESTRICT
              -> ON DELETE RESTRICT
              -> );
          

          我能想到的一个实用工具是一个快速修复,以确保在PRIMARY KEY column 中输入一个值后,它既不能更新也不能删除。

          例如,在这里让我们填充表格t1 -

          INSERT INTO t1 (c1) VALUES
              -> (1),
              -> (2),
              -> (3),
              -> (4),
              -> (5);
          
          SELECT * FROM t1;
          +----+
          | c1 |
          +----+
          |  1 |
          |  2 |
          |  3 |
          |  4 |
          |  5 |
          +----+
          

          现在,让我们尝试更新row1 -

          UPDATE t1
              -> SET c1 = 6 WHERE c1 = 1;
          ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`constraints`.`t1`, CONSTRAINT `fk` FOREIGN KEY (`c1`) REFERENCES `t1` (`c1`) ON DELETE RESTRICT ON UPDATE RESTRICT)
          

          现在,让我们尝试删除row1 -

          DELETE FROM t1
              -> WHERE c1 = 1;
          ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`constraints`.`t1`, CONSTRAINT `fk` FOREIGN KEY (`c1`) REFERENCES `t1` (`c1`) ON DELETE RESTRICT ON UPDATE RESTRICT)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2023-03-25
            • 2014-08-07
            • 2016-11-18
            • 2020-10-02
            • 1970-01-01
            • 1970-01-01
            • 2012-10-31
            相关资源
            最近更新 更多