结合INSERT和SELECT查询以生成唯一ID

时间:2013-08-28 17:45:55

标签: mysql sql uniqueidentifier

背景:在行为实验中,大黄蜂被标记有唯一标识符以跟踪其移动。问题是标签只有两位数,而殖民地可能只有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;

您是否有基于此数据生成主键的任何提示?

提前致谢! 列维

1 个答案:

答案 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。