2009-11-02 3 views
6

У меня есть 4 таблицы (назначенные, класс, избранные, статус), которые я хочу переместить ссылку в столбец одной таблицы (участников). Значения из 4 таблиц зависят от времени, исходя из таблицы истории (members_history). Желаемый результат состоит в том, что запрос должен выводить все члены и текущую назначенную позицию или текущую избранную позицию, класс и статус в строке членов и включать дополнительную информацию, полученную из иностранных строк.Динамические внешние ключи - как реализовать?

Таким образом, вместо того, чтобы просто возвращение:

идентификатор, имя пользователя, пароль, соль, name_first, name_last, date_join & date_leave;

Запрос будет возвращать

идентификатор, имя пользователя, пароль, соль, name_prefix, name_first, name_last, hours_extra, date_join, date_leave, appointed, class, elected & 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 является FK для идентификатора в таблице членов, не каждый член будет иметь историю на них (но в конце концов все они будут, поскольку каждый член должен будет иметь класс и статус). members_history.value - FK для members:{members_history.table}.id;

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); 

ответ

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; 

Эта таблица служит родительской таблицы для всех ваших членов атрибутов таблицы. Каждая из этих таблиц меняет свой первичный ключ на , а не автоматически генерирует значения идентификатора, но вместо этого ссылается на первичный ключ 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.

Теперь вы можете рассчитывать на ссылочной целостности и консистенции что невозможно при попытке реализовать полиморфные ассоциации без общего предка всех упомянутых таблиц атрибутов.

Вот запрос, который возвращает результат вы описали (протестирован на 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; 
+0

Отличный ответ Билл, большое спасибо. Я многому научился. –

+0

Очевидно, что «триггер» не входит в мои полномочия на производственном сервере, который я использую. Иди сюда. –

+0

Триггер довольно прост, только два заявления длинны. Вы можете сделать что-то эквивалентное в вашем коде приложения каждый раз, когда вы вставляете в одну из своих таблиц. –

1

Все, что вам нужно, это левое внешнее соединение для каждого из типов истории и любой логики, необходимой для выбора «текущей» строки.

Ваша структура таблицы не имеет достаточного смысла для того, чтобы собрать образец для вас. возможно, если вы предоставите один элемент элемента и пару строк истории для атрибута ONE, я могу вам помочь.

+0

я добавил некоторые детали о возможных значениях к исходному сообщению. –

+0

спасибо. см. ответ на счет. – longneck