【问题标题】:Dynamic Foreign Keys - How To Implement?动态外键 - 如何实现?
【发布时间】:2009-11-02 16:41:20
【问题描述】:

I have 4 tables (appointed, class, elected, status) that I want to cross reference into a single table's (members) column. 4 个表的值是基于历史表 (members_history) 的时间敏感的。所需结果是查询应输出所有成员和当前指定的位置或当前选举位置,类和状态在成员行中,并包括从外来行获得的其他信息。

所以不要只是返回:

id、用户名、密码、salt、name_first、name_last、date_join & date_leave;

查询将返回

id、用户名、密码、salt、name_prefix、name_first、name_last、hours_extra、date_join、date_leave、appointedclasselected & status

只要添加的列在历史记录中没有当前值,其结果应该为 NULL。

现在我想我可以用子查询来做到这一点,但到目前为止我的头一直在敲击键盘。稍后我会再试一试,但在那之前,还有其他人愿意试一试,或尝试为我指明正确的方向吗?

我的 SQL(没有双关语)表的结构如下:

CREATE TABLE IF NOT EXISTS `members` (
 `id` mediumint(3) unsigned NOT NULL auto_increment COMMENT 'Members Unique Id',
 `username` varchar(32) collate utf8_bin NOT NULL COMMENT 'Mebers Username',
 `password` varchar(64) collate utf8_bin NOT NULL COMMENT 'Members Password Hash',
 `salt` varchar(32) collate utf8_bin NOT NULL COMMENT 'Members Password Salt',
 `name_first` varchar(32) collate utf8_bin NOT NULL COMMENT 'Members First Name',
 `name_last` varchar(32) collate utf8_bin NOT NULL COMMENT 'Members Last Name',
 `date_join` date NOT NULL COMMENT 'Members Join Date',
 `date_leave` date default NULL COMMENT 'Members Resgination Date (If Applicable)',
 PRIMARY KEY  (`id`),
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Members id in this table = mid in other tables';

CREATE TABLE IF NOT EXISTS `members:apointed` (
 `id` tinyint(3) unsigned NOT NULL auto_increment COMMENT 'Unique value',
 `name_prefix` varchar(8) collate utf8_bin NOT NULL COMMENT 'Prefix Added to Members Name',
 `hours_extra` decimal(4,2) NOT NULL COMMENT 'Hours Given as Bonus for Holding this Position.',
 `position` varchar(40) collate utf8_bin NOT NULL COMMENT 'Name of the Posisiton',
 PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Undefined within the SOP or By-Laws.';

CREATE TABLE IF NOT EXISTS `members:class` (
 `id` tinyint(3) unsigned NOT NULL auto_increment COMMENT 'Unique Id',
 `class` varchar(8) collate utf8_bin NOT NULL COMMENT 'Unique Value',
 PRIMARY KEY  (`id`),
 UNIQUE KEY `value` (`class`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Article I, Section 1 Subsection B: Classes of Membership';

CREATE TABLE IF NOT EXISTS `members:elected` (
 `id` tinyint(3) unsigned NOT NULL auto_increment COMMENT 'Unique value',
 `name_prefix` varchar(8) collate utf8_bin NOT NULL COMMENT 'Prefix Added to Members Name',
 `hours_extra` decimal(4,2) NOT NULL COMMENT 'Hours Given as Bonus for Holding this Position.',
 `position` varchar(40) collate utf8_bin NOT NULL COMMENT 'Name of the Posisiton',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Article II';

CREATE TABLE IF NOT EXISTS `members:status` (
 `id` tinyint(3) unsigned NOT NULL auto_increment COMMENT 'Bit''s Place',
 `status` varchar(16) collate utf8_bin NOT NULL COMMENT 'Categorie''s Name',
 PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Article I, Section 1, Subsection A: Categories of Membership';

CREATE TABLE IF NOT EXISTS `members_history` (
 `id` int(10) unsigned NOT NULL auto_increment COMMENT 'Unique Id',
 `mid` tinyint(3) unsigned NOT NULL COMMENT 'Members Unique Id.',
 `table` enum('class','elected','appointed','status') NOT NULL COMMENT 'Name of Table that was Edited.',
 `value` tinyint(3) unsigned NOT NULL COMMENT 'Value',
 `start` date NOT NULL COMMENT 'Value''s Effect Date',
 `end` date default NULL COMMENT 'Value''s Expiration Date',
 PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COMMENT='Member History';

members_history.mid 是 members 表中 id 的 FK,并非每个成员都有历史记录(但最终他们都会有,因为每个成员都必须有一个类和状态)。 members_history.valuemembers:{members_history.table}.id 的 FK;

INSERT INTO `members`
(`id`, `username`, `password`, `salt`, `name_first`, `name_last`, `date_join`, `date_join`) VALUES
(   1,   'Dygear',MD5('pass'), 's417',       'Mark',    'Tomlin',      DATE(), NULL),
(   2,  'uberusr',MD5('p455'), '235f',     'Howard',    'Singer',      DATE(), NULL),
(   3,'kingchief',MD5('leet'), '32fs','Christopher',   'Buckham',      DATE(), NULL);

INSERT INTO `members:apointed`
(`id`, `name_prefix`, `hours_extra`, `posisiton`) VALUES
(   1,            '',          0.00, 'Crew Chief'),
(   2,            '',         20.00, 'Engineer'),
(   3,         'Lt.',         40.00, 'Lieutenant'),
(   4,       'Capt.',         60.00, 'Captin'),
(   5,      'Chief.',         80.00, '3rd Assistant Chief of Operation');

INSERT INTO `members:class`
(`id`, `class`) VALUES
(   1, 'Class I'),
(   2, 'Class II');

INSERT INTO `members:elected`
(`id`, `name_prefix`, `hours_extra`, `posisiton`) VALUES
(   1,            '',         40.00, 'Trustee'),
(   2,            '',         40.00, 'Chairman of the Board'),
(   3,       'Prez.',         40.00, 'President'),
(   4,      'VPrez.',         40.00, 'Vice-President'),
(   5,            '',         40.00, 'Recording Secretary'),
(   6,            '',         40.00, 'Service Secretary'),
(   7,            '',         40.00, 'Corresponding Secretary'),
(   8,            '',         40.00, 'Financial Secretary Treasuer'),
(   9,            '',         40.00, 'Assistant Financial Secretary Treasuer'),
(  10,      'Chief.',         80.00, 'Chief of Operations'),
(  11,      'Chief.',         80.00, 'First Deputy Chief of Operations'),
(  12,      'Chief.',         80.00, 'Second Deputy Chief of Operation');

INSERT INTO `members:status`
(`id`, `status`) VALUES
(   1, 'Active'),
(   2, 'Inactive'),
(   3, 'Student'),
(   4, 'Probationary'),
(   5, 'Lifetime'),
(   6, 'Cadet'),
(   7, 'Honorary'),
(   8, 'Medical'),
(   9, 'Military'),
(  10, 'Resigned'),
(  11, 'Disvowed');


INSERT INTO `members_history`
(`id`, `mid`,    `table`, `value`, `start`, `end`) VALUES
(NULL,     1, 'apointed',       3,  DATE(), NULL),
(NULL,     1,    'class',       1,  DATE(), NULL),
(NULL,     1,   'status',       1,  DATE(), NULL),
(NULL,     2,  'elected',       4,  DATE(), NULL),
(NULL,     2,    'class',       1,  DATE(), NULL),
(NULL,     2,   'status',       1,  DATE(), NULL),
(NULL,     3, 'apointed',      10,  DATE(), '2010-05-01'),
(NULL,     3,    'class',       1,  DATE(), NULL),
(NULL,     3,   'status',       1,  DATE(), NULL);

【问题讨论】:

    标签: mysql sql foreign-keys polymorphic-associations


    【解决方案1】:

    您正在使用一种称为多态关联的设计,但它经常做错。让它工作的方法是创建另一个表,比如members:abstract

    CREATE TABLE IF NOT EXISTS `members:abstract` (
     `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
     `type` enum('class','elected','appointed','status') NOT NULL,
      UNIQUE KEY (`id`, `type`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    

    此表用作所有成员属性表的父表。这些表中的每一个都将其主键更改为自动生成 id 值,而是引用members:abstract 的主键。我将只显示members:appointed,但其他类似。

    CREATE TABLE IF NOT EXISTS `members:appointed` (
     `id` INT UNSIGNED NOT NULL PRIMARY KEY, -- not auto_increment
     `name_prefix` varchar(8) collate utf8_bin NOT NULL COMMENT 'Prefix Added to Members Name',
     `hours_extra` decimal(4,2) NOT NULL COMMENT 'Hours Given as Bonus for Holding this Position.',
     `position` varchar(40) collate utf8_bin NOT NULL COMMENT 'Name of the Posisiton',
     FOREIGN KEY (`id`) REFERENCES `members:abstract` (`id`) ON DELETE CASCADE
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Undefined within the SOP or By-Laws.';
    

    您可以使用触发器使此表自动获得自动生成的值:

    DELIMITER //
    DROP TRIGGER IF EXISTS ins_appointed//
    CREATE TRIGGER ins_appointed BEFORE INSERT ON `members:appointed`
    FOR EACH ROW BEGIN
      INSERT INTO `members:abstract` (`type`) VALUES ('appointed');
      SET NEW.id = LAST_INSERT_ID();
    END; //
    DELIMITER ;
    

    对其他每个属性表执行相同操作。

    请注意,id 值现在在所有属性表中都是唯一的。

    接下来,您将members:abstract 设置为members_history 中外键的目标。

    CREATE TABLE IF NOT EXISTS `members_history` (
     `id` INT unsigned NOT NULL auto_increment COMMENT 'Unique Id',
     `mid` INT unsigned NOT NULL COMMENT 'Members Unique Id.',
     `value` INT UNSIGNED NOT NULL,
     `table` enum('class','elected','appointed','status') NOT NULL,
     `start` date NOT NULL COMMENT 'Value''s Effect Date',
     `end` date default NULL COMMENT 'Value''s Expiration Date',
     PRIMARY KEY (`id`),
     FOREIGN KEY (`mid`) REFERENCES `members` (`id`) ON DELETE CASCADE,
     FOREIGN KEY (`value`, `table`) REFERENCES `members:abstract` (`id`, `type`) ON DELETE CASCADE
    ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COMMENT='Member History';
    

    请注意,该表定义了一个外键,因此您不能members:abstract 中引用错误类型属性的 id。

    现在您可以依赖参照完整性一致性,当您尝试在没有所有引用属性表的公共父级的情况下实现多态关联时,这是不可能的。

    这是返回您描述的结果的查询(在 MySQL 5.1.40 上测试):

    SELECT m.username,
      m.password, m.salt, m.name_first, m.name_last,
      MAX(a.name_prefix) AS name_prefix,
      COALESCE(MAX(a.hours_extra), MAX(e.hours_extra)) AS hours_extra,
      MAX(m.date_join) AS date_join,
      MAX(m.date_leave) AS date_leave,
      MAX(a.position) AS appointed,
      MAX(c.class) AS class,
      MAX(e.position) AS elected,
      MAX(s.status) AS status
    FROM `members` m 
    JOIN `members_history` h ON (h.mid = m.id)
    LEFT OUTER JOIN `members:appointed` a ON (h.table = 'appointed' AND h.value = a.id)
    LEFT OUTER JOIN `members:class` c ON (h.table = 'class' AND h.value = c.id)
    LEFT OUTER JOIN `members:elected` e ON (h.table = 'elected' AND h.value = e.id)
    LEFT OUTER JOIN `members:status` s ON (h.table = 'status' AND h.value = s.id)
    GROUP BY m.id;
    

    【讨论】:

    • 出色的答案比尔,非常感谢。我从中学到了很多。
    • 显然“触发器”不在我使用的生产服务器上的权力范围内。去图吧。
    • 触发器很简单,只有两条语句。每次插入其中一个表时,您都可以在应用程序代码中执行等效操作。
    • 是的,后来我想通了。要知道,直到现在我才真正欣赏到它的质量。真的,你在这方面做得很棒。非常感谢。
    • 在按照members:abstractmembers:appointedmembers:classmembers:electedmembers:status 的顺序制作每个表之后,没有明显的原因,然后当它开始制作 members_history 时失败并出现此错误:#1005 - Can't create table './wlvacc_ems/members_history.frm' (errno: 150).
    【解决方案2】:

    您所需要的只是每个历史记录类型的左外连接以及选择“当前”行所需的任何逻辑。

    您的表格结构对我来说没有足够的意义,无法为您整理一个示例。也许如果您提供一个示例成员和几行 ONE 属性的历史记录,我可以帮助您。

    【讨论】:

    • 我在原始帖子中添加了一些关于可能值的详细信息。
    猜你喜欢
    • 2013-10-25
    • 1970-01-01
    • 2022-08-24
    • 1970-01-01
    • 2018-01-15
    • 2017-10-21
    • 1970-01-01
    • 2022-01-17
    • 2011-04-09
    相关资源
    最近更新 更多