动态外键 - 如何实现?

时间:2009-11-02 16:41:21

标签: mysql sql foreign-keys polymorphic-associations

我有4个表(指定,类,选举,状态),我想将其引用到单个表的(成员)列中。基于历史表(members_history),4个表的值是时间敏感的。期望的结果是查询应输出成员行中的所有成员和当前指定的位置或当前选定的位置,类和状态,并包括从外部行获得的其他信息。

所以不要只回来:

id,username,password,salt,name_first,name_last,date_join& date_leave;

查询将返回

id,username,password,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是成员表中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);

2 个答案:

答案 0 :(得分:11)

您使用的是名为多态关联的设计,但它经常出错。使它工作的方法是创建另一个表,比如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;

此表用作所有成员属性表的父表。这些表中的每一个都将其主键更改为 not 自动生成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

请注意,此表定义了一个外键,因此无法引用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; 中错误类型属性的ID。

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

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

{{1}}

答案 1 :(得分:1)

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

你的桌面结构对我来说没有足够的意义为你准备一个样本。也许如果你为ONE属性提供一个样本成员和几行历史记录,我可以帮助你。