2009-11-02 11 views
6

Tengo 4 tablas (designadas, clase, elegidas, estado) que quiero hacer referencia cruzada en la columna de una sola tabla (miembros). Los valores de las tablas de 4 son sensibles al tiempo basados ​​en una tabla de historial (members_history). El resultado deseado es que la consulta debe mostrar todos los miembros y el puesto actual designado o la posición, clase y estado elegidos actuales dentro de la fila de miembros e incluir información adicional obtenida de las filas externas.Teclas dinámicas foráneas: ¿cómo implementarlas?

Así que en lugar de devolver:

ID, nombre de usuario, contraseña, sal, name_first, name_last, date_join & date_leave;

La consulta devolvería

ID, nombre de usuario, contraseña, sal, name_prefix, name_first, name_last, hours_extra, date_join, date_leave, appointed, class, elected & status;

Donde una columna agregada no tiene un valor actual en el historial, su resultado debe ser NULO.

Ahora creo que puedo hacer esto con sub-querys, pero hasta ahora he estado golpeando mi cabeza contra el teclado. Daré otro golpe más tarde, pero hasta entonces, ¿alguien más dispuesto a intentarlo o intentar señalarme en la dirección correcta?

La estructura de mi SQL (sin doble sentido) tablas es el siguiente:

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 es una FK id en la tabla miembros, no todos los miembros tendrá la historia de ellos (pero con el tiempo todos lo harán, ya que cada miembro tendrá que tener una clase y un estado). members_history.value es un FK para 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); 

Respuesta

11

Estás utilizando un diseño llamado asociaciones polimórficas y con frecuencia se hace mal. La manera de hacer que funcione es crear otra tabla, members:abstract decir:

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; 

Esta tabla sirve como la tabla primaria para todos sus miembros atributos de tablas. Cada una de estas tablas cambia su clave principal a no genera valores de identificación automáticamente, sino que hace referencia a la clave principal de members:abstract. Mostraré solo members:appointed pero los otros serían similares.

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.'; 

puede mostrar los valores generados automáticamente esta ganancia mesa de forma automática con un disparador:

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 ; 

hacer lo mismo para cada una de las otras tablas de atributos.

Tenga en cuenta que los valores de id ahora son únicos en todas sus tablas de atributos.

A continuación, realice members:abstract el destino para una clave externa en 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'; 

Tenga en cuenta que esta tabla define una clave externa de modo que usted no puede referencia un identificador del tipo incorrecto de atributo en members:abstract.

Ahora se puede confiar en la integridad referencial y consistencia que es imposible cuando se trata de poner en práctica las asociaciones polimórficas sin que el padre común de todas las tablas de atributos referenciados.

Aquí está la consulta que devuelve el resultado que se describe (probado con 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

Respuesta pendiente Bill, muchas gracias. Aprendí mucho de eso. –

+0

Aparentemente el "disparador" no está dentro de mis posibilidades en el servidor de producción que uso. Ve figura, ¿eh? –

+0

El disparador es bastante simple, solo dos enunciados largos. Puede hacer algo equivalente en el código de su aplicación cada vez que lo inserte en una de sus tablas. –

1

todo lo que necesita es una unión externa izquierda para cada uno de los tipos de historial y cualquier lógica que necesite para elegir la fila "actual".

estructura de la tabla no tiene suficiente sentido para mí para armar una muestra para usted. tal vez si proporciona UN miembro de muestra y un par de filas de historial para UN atributo, puedo ayudarlo.

+0

He añadido algunos detalles acerca de posibles valores para el post original. –

+0

gracias. por favor vea la respuesta de Bill ahora. – longneck