0

背景:在一项行为实验中,大黄蜂被标记了一个唯一标识符以跟踪它们的运动。问题是标签只有 2 位数字,而群体可以多达 500 个人。这使得生成主键变得具有挑战性。

表格

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

您对根据这些数据生成主键有什么建议吗?

提前致谢!列维

4

1 回答 1

1

使这更易于管理的方法取决于您想要进行多少重构。:) 对这个问题做出回应很棘手,因为我试图尽可能接近你已经得到的东西,而不是建议进行重大改革。

这不完全是一个普遍感兴趣的问题,但我认为下面的一些信息可能更广泛适用。其中一些反映了我经常使用的技术,以将更多的智能保留在数据库中,而不是使用数据库的应用程序。

由于没有“标记蜜蜂”的基表,因此存在的当前结构不能提供一种干净的方式来保持关系完整性。标签表,据我所知,更像是一个蜜蜂标记事件表。在第一次阅读时,我认为这是一张表,其中每一行都是一只蜜蜂,但看起来每只蜜蜂可以由 2 行表示(可能更多,因为结构并不清楚可能的数据值可能是什么.)

以下是一些一般性观察。

对于您的VARCHAR(255)字段(危险信号!),请查看 MySQL 的ENUM数据类型。有几列似乎只支持一小组可能的有效值。一个例子:

events VARCHAR(255) DEFAULT NULL,      /* replace this */
events ENUM('birth','death') NOT NULL,  /* with this */

列就像有一个ENUM查找表,没有表,并且适用于只有少量可能的有效值的列。您不能在ENUM列中输入无效值,并且表会更小,因为ENUM列通常每行只需要一个字节的数据来存储输入的值。

您的表似乎没有任何索引。当数据集较小时,您可能不会注意到差异,但随着数据集的增长,适当的索引会产生巨大的性能差异。

关于问题的实质,“真正的问题”似乎不仅仅是如何选择主键,而是如何能够保证数据的完整性,以便您基于收集的数据进行后续分析不会不准确.

请注意,例如,您在 (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 表)时,您担心的是观察结果与正确的蜜蜂相关联,理想情况下无需在“标签”表中查找蜜蜂。

如果“标签”表只有出生和死亡事件,您可以将其重新设计为一个表,其中每一行代表单个蜜蜂上的单个标签。在表中添加birth_date和death_date列,然后使用tag中的id作为主键,去掉“bee_id”,在exp8中插入“tag_id”,外键引用tag(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 的大部分工作都涉及将不良数据排除在数据库之外——但是这个触发器为您提供了一个可能的例子。如果已经有一只蜜蜂的死亡日期为空,并且您尝试插入的标签与您尝试插入的标签相同,那么这将使该表在该日期具有该标签的蜜蜂的身份方面与自身不一致;触发器将阻止插入并显示错误消息。触发器可以防止不适当的BEFORE UPDATE修改,例如,将蜜蜂的 dead_date 更改为具有相同标签的现有蜜蜂的birth_date 之后的日期。

我希望这能提供一些有用的指示。该代码至少需要 MySQL Server 5.5,因此您可以使用 5.6.12。

于 2013-08-30T03:16:37.530 回答