Mysql 组合INSERT和SELECT查询以生成唯一的ID

Mysql 组合INSERT和SELECT查询以生成唯一的ID,mysql,sql,uniqueidentifier,Mysql,Sql,Uniqueidentifier,背景:在一项行为实验中,大黄蜂被标记上一个唯一的标识符来跟踪它们的运动。问题是,标签只有两位数,而菌落可以多达500个个体。这使得生成主键具有挑战性 桌子 (1) 。每个选项都记录在此表中: CREATE TABLE `exp8` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `bee_id` varchar(255) DEFAULT NULL, `date_time` datetime DEFAULT NULL, `choic

背景:在一项行为实验中,大黄蜂被标记上一个唯一的标识符来跟踪它们的运动。问题是,标签只有两位数,而菌落可以多达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);
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');
(2) 。标签的开始和结束日期记录在此表中:

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);
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;
您是否有任何关于基于此数据生成主键的提示

提前谢谢!
Levi

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

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

现有的结构并不能提供一种保持关系完整性的干净方法,因为没有“标记的蜜蜂”的基表。从我可以看出,标记表更像是蜜蜂标记事件的表。在第一次阅读时,我认为这是一个表,其中每一行都是一个单独的蜜蜂,但看起来每个蜜蜂可以由2行表示(可能更多,因为结构没有明确可能的数据值)

以下是一些一般性的观察结果

对于
VARCHAR(255)
字段(红旗!),请查看MySQL的数据类型。有几个列似乎只支持一小部分可能的有效值。一个例子:

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开始的最小标记日期大于外部查询的标记日期,它只要求“不超过1条记录”,并且只有在数据库恰好返回正确记录时才能正常工作,这通常是可能的,但决不是确定的。数据库可以自由地返回任何有效记录来响应这样的查询,您不应该总是依赖它来完成它现在可能正在做的事情。这样写得更准确:

t2.tag_date >= t.tag_date ORDER BY t2.tag_date limit 1
(第(3)处的查询也很难理解,因为您正在重用“t”别名来表示两种不同的含义。)

如果我理解正确,当您输入观察结果(exp8表)时,您关心的是观察结果与正确的蜜蜂相关联,理想情况下无需在“标记”表中查找蜜蜂

如果“tag”表只有出生和死亡事件,您可以将其重新设计为一个表,其中每行表示单个蜜蜂上的单个标记。将出生日期和死亡日期列添加到表中,然后将id from标记用作主键,删除“bee_id”并在exp8中插入“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 ;
tag_id的值将是函数的返回值,如果找不到该日期带有该标记的有效bee,函数将抛出错误。如果标记表中有不明确的数据表明观察时有多个蜜蜂使用同一标记活着,那么它还会抛出一个
子查询,返回多行
错误

您可以更进一步,假设修改后的标记表包含出生日期和死亡日期,并使用触发器对标记表中的日期范围施加一些健全性

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,我的大部分工作都涉及将坏数据排除在数据库之外——但这个触发器为您提供了一个可能的示例。如果已经有一只蜜蜂的死亡日期为空,并且你正试图插入相同的标签,这将使该表在该日期带有该标签的蜜蜂的身份方面与其自身不一致;触发器将用错误消息阻止插入。更新前的
触发器可以防止不适当的修改,例如,将蜜蜂的死亡日期更改为具有相同标签的现有蜜蜂的出生日期之后的日期


我希望这能提供一些有用的指导。代码至少需要MySQL服务器5.5,所以您可以使用5.6.12。

什么的主键?每只蜜蜂?是的,每只蜜蜂都有一个主键,用来区分使用相同标签的蜜蜂。我可能过于简单化了,但是如果
bee\u id
是两位数的标签代码,