【问题标题】:MySQL Database design. Inserting rows in 1to1 tables.MySQL 数据库设计。在 1to1 表中插入行。
【发布时间】:2011-07-14 09:54:47
【问题描述】:

将行插入到相互引用 1 到 1 的表中的最佳方法是什么?

我的意思是,在 MySQL 5.5 和 InnoDB 表中,我有一个类似于以下的数据库设计

当我们尝试在 table1 和 table2 中插入行时,问题就出现了。由于 MySQL 中没有多表插入,所以我不能插入一行,因为两个表中的外键都是 NOT NULL 字段,应该同时插入两个表中。

解决这个问题的最佳方法是什么?

我想到了 3 种可能的解决方案,但我想知道是否有更多的解决方案,或者哪个是最好的以及为什么。

  1. 将外键字段设置为NULLABLE,在表中插入一行后,插入另一行,然后更新第一个。

  2. 如上所示,但具有特殊值,例如 -1。首先,插入一个具有外部key = -1 的表,这相当于NULL,但避免将该字段设置为NULLABLE。之后,我们在另一个表中插入该行并更新第一个插入的行。

  3. 在两者之间创建一个关系表,尽管它并不是真正必要的,因为它是 1 比 1 的比率

谢谢!!

编辑 我简要解释一下我需要这种循环关系:它是从父表到其子表的非规范化。始终具有父表中排名最高的子项的参考是为了提高性能而制定的。

【问题讨论】:

  • 我经常观察到这种关系是两个表应该在一个表中,或者只有一个表需要引用。
  • 在这种情况下是不正确的。无论如何,¡谢谢!
  • @Emilio:您可能认为在这种情况下它是不正确的。但也许有办法改变这两张桌子的设计,所以没有这样的圆形路径。你能描述一下为什么需要这两种关系吗?这两张表是干什么用的?
  • @Emilio:这是一个常见的设计缺陷。如果两张表是1:1关系,那你为什么不一张表呢?
  • 这是从父表到其子表的非规范化。始终从父表中获取排名最高的子项的参考是为了实现高性能。

标签: mysql database database-design insert relational-database


【解决方案1】:

我会回答这个问题,因为我觉得这是一个设计缺陷。

首先,如果这两个表是真正的1:1 关系,为什么不只有一个表?


其次,如果它不是真正的1:1 关系而是超类型-子类型问题,你也不需要这个循环外键。假设table1Employeetable2Customer。当然,大多数客户不是员工(反之亦然)。但有时客户也可能是员工。这可以通过 3 个表来解决:

Person
------
id
PRIMARY KEY: id

Employee
--------
personid
lastname
firstname
... other data
PRIMARY KEY: personid
FOREIGN KEY: personid
    REFERENCES Person(id)

Customer
--------
personid
creditCardNumber
... other data
PRIMARY KEY: personid
FOREIGN KEY: personid
    REFERENCES Person(id)

在您描述的场景中,您有两个表 ParentChild 具有 1:N 关系。然后,您想以某种方式为每个父母存储表现最好(基于定义的计算)的孩子。

这行得通吗?:

Parent
------
id
PRIMARY KEY: id

Child
-----
id
parentid
... other data
PRIMARY KEY: id
FOREIGN KEY: parentid
    REFERENCES Parent(id)
UNIQUE KEY: (id, parentid)             --- needed for the FK below

BestChild
---------
parentid
childid
... other data
PRIMARY KEY: parentid
FOREIGN KEY: (childid, parentid)
    REFERENCES Child(id, parentid)

这样,您可以强制执行所需的引用完整性(每个 BestChild 都是一个 Child,每个 Parent 都只有一个 BestChild)并且引用中没有循环路径。对最佳子项的引用存储在额外表中,而不是Parent 表中。

您可以通过加入为每位家长找到 BestChild:

Parent
  JOIN BestChild
    ON Parent.id = BestChild.parentid
  JOIN Child
    ON BestChild.childid = Child.id

另外,如果你想为多个性能测试存储最好的孩子(不同类型的测试,或不同日期的测试),你可以添加一个test字段,并将主键更改为(test, parentid)

BestChild
---------
testid
parentid
childid
... other data
PRIMARY KEY: (testid, parentid)
FOREIGN KEY: (childid, parentid)
    REFERENCES Child(id, parentid)
FOREIGN KEY: testid
    REFERENCES Test(id)

【讨论】:

  • 感谢您的回答!我已经编辑了这个问题,以澄清为什么需要这种关系。
  • @Emilio:也选中此选项。不涉及触发器,没有循环引用,并且额外的表被规范化。 (好的,如果您希望每次孩子的属性发生变化时更新性能最好的孩子,您可能会有触发器)。
【解决方案2】:

我会创建一个黑洞表并在其上放置一个触发器来处理插入

CREATE TABLE bh_table12 (
  table1col varchar(45) not null,
  table2col varchar(45) not null
) ENGINE = BLACKHOLE

并在其上放置一个触发器以处理插入

DELIMITER $$

CREATE TRIGGER ai_bh_table12_each AFTER INSERT ON bh_table12 FOR EACH ROW
BEGIN
  DECLARE mytable1id integer;
  DECLARE mytable2id integer;

  SET foreign_key_checks = 0;
    INSERT INTO table1 (table1col, table2_id) VALUES (new.table1col, 0);
    SELECT last_insert_id() INTO mytable1id;
    INSERT INTO table2 (table2col, table1_id) VALUES (new.table2col, table1id);
    SELECT last_insert_id() INTO mytable2id;
    UPDATE table1 SET table2_id = mytable2id WHERE table1.id = mytable1id;
  SET foreign_key_checks = 1;
END $$

DELIMITER ;

请注意,触发器中的操作是一个事务的一部分(使用 InnoDB 或类似情况时),因此触发器中的错误将回滚部分更改。

注意表结构
请注意,如果是 1 对 1 表,则只需在 table1 中放置 table2_id,在 table2 中不放置 table1_id(反之亦然)。
如果您需要根据 table2 查询 table1,您可以使用:

SELECT table1.* FROM table1
INNER JOIN table2 on (table2.id = table1.table2_id)
WHERE table2.table2col = 'test2'

反之亦然

SELECT table2.* FROM table2
INNER JOIN table1 on (table2.id = table1.table2_id)
WHERE table1.table1col = 'test1'

链接:
http://dev.mysql.com/doc/refman/5.1/en/blackhole-storage-engine.html
http://dev.mysql.com/doc/refman/5.1/en/triggers.html

【讨论】:

  • 感谢您的回答!我已经编辑了这个问题,以澄清为什么需要这种关系。但是我不知道黑洞引擎,请你解释一下。再次感谢!
  • blackhole 引擎是一个数据库存储,它会忘记您放入其中的所有内容,您可以将所有内容放入其中,但什么都不会出现(就像 Linux 上的 /dev/null 一样)。这很有用,因为您可以将日志文件和触发器附加到黑洞,使其无需清理即可完成工作。
【解决方案3】:

我觉得这是一个重要的问题,而且我还没有在整个网络上找到任何 100% 令人满意的答案。您给出的 2 个答案是我找到的最好的答案,但它们并不是 100% 令人满意。

原因如下:

  • 我想 Emilio 不能将他最好的孩子放在他的父表中的原因很简单,因为我有同样的问题:不是每个孩子都会被标记为父母最好的孩子。所以他仍然需要将其他孩子的信息存储在其他地方。在这种情况下,他会在他们的父表中获得一些关于最好孩子的信息,而其他孩子则在一个单独的数据库中。这是一个巨大的混乱。例如,他想改变关于children的数据结构的那一天,他需要在两个表中都改变它。每次他对所有孩子写查询时,他都应该查询两个表,等等......

  • Emilio 不能只将最佳子外键设置为可为空的原因(我假设 Emilio,但对我来说会非常严格),因为他需要确保父母始终拥有最好的孩子。在 Emilio 的情况下,这可能不是很容易想象,但在我的情况下,我不能让父母没有孩子。

因此,我倾向于认为将 foreign_key_checks 设置为零的解决方案是最好的,但问题是:

  • 将foreign_key_checks 设置回1 后,不再检查数据的一致性。因此,您有在此期间犯错误的风险。你可以认为你不会,但这仍然不是一个非常干净的解决方案。

【讨论】:

    猜你喜欢
    • 2014-01-12
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 2012-06-01
    • 2021-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多