背景:在行为实验中,大黄蜂被标记有唯一标识符以跟踪其移动。问题是标签只有两位数,而殖民地可能只有500个人。这使得生成主键变得具有挑战性。
TABLES
(1)。每个选项都记录在此表中:
CREATE TABLE `exp8` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`bee_id` varchar(255) DEFAULT NULL,
`date_time` datetime DEFAULT NULL,
`choice` varchar(255) DEFAULT NULL,
`hover_duration` int(11) DEFAULT NULL,
`antennate_duration` int(11) DEFAULT NULL,
`land_duration` int(11) DEFAULT NULL,
`landing_position` varchar(255) DEFAULT NULL,
`remarks` longtext,
`validity` int(11) DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=264;
LOCK TABLES `exp8` WRITE;
/*!40000 ALTER TABLE `exp8` DISABLE KEYS */;
INSERT INTO `exp8` (`id`, `bee_id`, `date_time`, `choice`, `hover_duration`, `antennate_duration`, `land_duration`, `landing_position`, `remarks`, `validity`)
VALUES
(1,NULL,'2013-05-14 15:38:31','right',1,0,0,NULL,NULL,1),
(2,NULL,'2013-05-18 10:27:15','left',1,0,0,NULL,NULL,1),
(3,'G5','2013-05-18 11:44:44','left',0,0,4,'yellow',NULL,1),
(4,'G5','2013-06-01 10:00:00','left',0,0,4,'yellow',NULL,1);
(2)。标签的开始和结束日期记录在此表中:
CREATE TABLE `tags` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`bee_id` varchar(255) DEFAULT NULL,
`tag_date` date DEFAULT NULL,
`colony_id` int(11) DEFAULT NULL,
`events` varchar(255) DEFAULT NULL,
`worker_age` varchar(255) DEFAULT NULL,
`tagged_by` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=406;
LOCK TABLES `tags` WRITE;
/*!40000 ALTER TABLE `tags` DISABLE KEYS */;
INSERT INTO `tags` (`id`, `bee_id`, `tag_date`, `colony_id`, `events`, `worker_age`, `tagged_by`)
VALUES
(1,'G5','2013-05-08',1,'birth','Adult','ET'),
(2,'G5','2013-05-20',NULL,'death','Adult','ET'),
(3,'G5','2013-05-29',1,'birth','Adult','ET');
(3)。区分标签多次使用的查询:
select t.bee_id,
(case when t.death_date is null then 'Alive' else 'Dead' end) as status,
t.tag_date,
t.death_date,
(case when t.death_date is not null then timediff(t.death_date,t.tag_date)
else timediff(NOW(),t.tag_date) end) as age
from (select t.*,
(select t2.tag_date
from tags t2
where t2.bee_id = t.bee_id and
t2.events = 'death' and
t2.tag_date >= t.tag_date
limit 1
) as death_date
from tags t
where t.events = 'birth'
) t
group by t.bee_id, t.tag_date;
您是否有基于此数据生成主键的任何提示?
提前致谢! 列维
答案 0 :(得分:1)
让这个更容易管理的方法取决于你有多少重构的心情。 :)制定对这个问题的回答是棘手的,因为我试图保持与你已经得到的接近,而不是建议进行重大改革。
这不是一般利益问题,但我认为下面的一些信息可能更广泛适用。其中一些反映了我经常使用的技术,以便将更多的智能保存在数据库中,并保存在使用数据库的应用程序之外。
当前存在的结构不提供保持关系完整性的简洁方法,因为没有标记为蜜蜂的基表。"从我所知的标签表更像是一个蜜蜂标记事件表。在第一次阅读时,我认为这是一个表格,其中每一行都是一只蜜蜂,但看起来每只蜜蜂可以用2行代表(可能更多,因为结构不清楚可能的数据值是什么可能是。)
以下是一些一般性意见。
对于VARCHAR(255)
字段(红旗!),请查看MySQL的ENUM
数据类型。有几列似乎只支持一小组可能的有效值。一个例子:
events VARCHAR(255) DEFAULT NULL, /* replace this */
events ENUM('birth','death') NOT NULL, /* with this */
ENUM
列就像有一个没有表的查找表,并且适用于只有少量可能有效值的列。您无法在ENUM
列中输入无效值,并且该表格会更小,因为ENUM
列通常每行只需要一个字节的数据来存储输入的值。
您的表格似乎没有indexes。当数据集较小时,您可能不会注意到差异,但随着数据集的增长,适当的索引将产生显着的性能差异。
关于问题的实质,"真正的问题"似乎不仅仅是如何选择主键,而是如何确保数据完整性,以便您基于收集的数据的后续分析不会是不准确的。
请注意,例如,(3)中最内层的子查询不是确定性的:
t2.tag_date >= t.tag_date limit 1
这不会向数据库询问t2中最低的tag_date是否大于来自外部查询的tag_date,它只要求"不超过1条记录"并且只有在数据库碰巧返回正确的记录时才能正常工作,这通常可能但并非确定。数据库可以自由地返回任何有效记录以响应这样的查询,你不应该依赖它总是做它现在正在做的事情。这样写得更准确:
t2.tag_date >= t.tag_date ORDER BY t2.tag_date limit 1
((3)中的查询也很难理解,因为你重复使用" t"别名来表示两种不同的东西。)
如果我理解正确,当你输入观察结果(exp8表)时,你的担心是观察与正确的蜜蜂相关联,理想情况下,无需在#34;标签中查找蜜蜂&# 34;表
如果"标签"表格只有生育和死亡事件,您可以将其重新设计为一个表格,其中每一行代表一只蜜蜂上的单个标签。将birth_date和death_date列添加到表中,然后使用tag中的id作为主键,删除" bee_id"并插入" tag_id"在exp8中,外键引用标记(id)。
您正在寻找的主键。
然后您可以省略(3)处的查询,并通过tag.id = exp8.tag_id上的tag_id和exp8之间的简单连接获取与观察相关的蜜蜂信息。
创建一个函数,根据日期和标记进行查找。这将获取标签代码和观察日期,并使用它来查找(重新设计的)标签表中蜜蜂的ID。
DELIMITER $$
DROP FUNCTION IF EXISTS find_tag_id $$
CREATE FUNCTION find_tag_id (in_tag VARCHAR(2), in_event_date DATETIME) RETURNS int
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE new_tag_id INT DEFAULT NULL;
SET new_tag_id = (SELECT id FROM tag
WHERE bee_id = in_tag
AND birth_date <= in_event_date
AND (death_date IS NULL OR death_date >= DATE(in_event_date));
IF new_tag_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'no such bee'; /* force an error */
END IF;
RETURN new_tag_id;
END $$
DELIMITER ;
SELECT find_tag_id('2 digit code','some datetime');
会从标记表中返回匹配的ID,如果没有,则会抛出错误。
然后,您可以在插入查询中嵌入此函数,如下所示......
INSERT INTO `exp8` (`id`, `tag_id`, `date_time`, `choice` ...
VALUES (3,find_tag_id('G5','2013-05-18 11:44:44'),'2013-05-18 11:44:44','left' ...
tag_id的值将是函数的返回值,如果找不到该日期的那个标签的有效蜜蜂,该函数将抛出错误。如果标签表中存在含糊不清的数据,表明在观察时有多个蜜蜂在同一个标签上存活,它也会抛出Subquery returns more than one row
错误。
你可以更进一步,假设修改后的标签表有出生和死亡日期,并在标签表的日期范围内强加一些理智,带有触发器。
DELIMITER $$
DROP TRIGGER IF EXISTS tag_bi $$
CREATE TRIGGER tag_bi BEFORE INSERT ON tag FOR EACH ROW
BEGIN
IF EXISTS (SELECT * FROM tag WHERE bee_id = NEW.bee_id AND death_date IS NULL) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'there is already a living bee with the specified bee_id';
END IF;
END $$
DELIMITER ;
要实现完整性,还有很多工作要做 - 我作为DBA的大部分工作都涉及将不良数据保存在数据库之外 - 但是这个触发器为您提供了可能的事物类型的示例。如果已经有一个带有null death_date的蜜蜂,并且您尝试插入相同的标记,则该表将使该表与该标记的蜜蜂身份不一致在那个日期;触发器将阻止插入错误消息。 BEFORE UPDATE
触发器可以防止不适当的修改,例如,将蜜蜂的death_date更改为具有相同标记的现有蜜蜂的birth_date之后的日期。
我希望这提供了一些有用的指示。该代码至少需要MySQL Server 5.5,因此您可以使用5.6.12。