我的数据库中有一个未规范化的表,其名称为 details
结构,示例数据如下所示(图像道歉,只是认为它更容易理解):
我的挑战是使用分隔符 |
将列分割为 assignee, inventor and ipcsubclass
到新表{ detail_inv
和 inventors
},{ detail_asg
和 assignees
}以及{ detail_ipc
和 ipcsubclasses
} .
在所有三种情况下,表模式都类似 . 例如,发明人表_1154776_和 name
以及detail_inv表 detail_id
和 inventor_id
上的列 . 每行必须只有一个名称,发明人表中的所有名称都是唯一的,而且在detail_inv表中保存关系的ID .
我尝试使用以下代码为发明者存储过程 - 我为3列做了3个程序:(
drop procedure if exists normalise_details;
delimiter #
create procedure normalise_details()
proc_main:begin
declare v_cursor_done int unsigned default 0;
declare v_post_id int unsigned;
declare v_tags varchar(2048);
declare v_keyword varchar(50);
declare v_keyword_id mediumint unsigned;
declare v_tags_done int unsigned;
declare v_tags_idx int unsigned;
declare v_cursor cursor for select id, inventor from details order by id;
declare continue handler for not found set v_cursor_done = 1;
set autocommit = 0;
open v_cursor;
repeat
fetch v_cursor into v_post_id, v_tags;
set v_tags_done = 0;
set v_tags_idx = 1;
while not v_tags_done do
set v_keyword = substring(v_tags, v_tags_idx,
if(locate('|', v_tags, v_tags_idx) > 0,
locate('|', v_tags, v_tags_idx) - v_tags_idx,
length(v_tags)));
if length(v_keyword) > 0 then
set v_tags_idx = v_tags_idx + length(v_keyword) + 1;
set v_keyword = trim(v_keyword);
insert into inventors (name) values (v_keyword);
select id into v_keyword_id from inventors where name = v_keyword;
insert into details_inv (inventor_id, detail_id) values (v_keyword_id, v_post_id);
else
set v_tags_done = 1;
end if;
end while;
until v_cursor_done end repeat;
close v_cursor;
commit;
end proc_main #
delimiter ;
当我在一些随机测试数据上尝试这个时,它工作正常 . 当我在实际 table 上这样做时,它不能很好地工作 . 仅插入部分数据 . SQL不会抛出任何错误(有些时候除外:“#1172 - 结果由多行组成”或“inventor_id列不能为空”)
我尝试修改MySQL - Insert comma separated list into normalized tables via stored procedure的代码以满足我的需求,但我失败了 .
请帮助我,我的数据库表变得一团糟,大约有500,000行,这使我很难在每个项目上爆炸和管理大型数组(最近的项目有大约200,000行) .
2 回答
看看RolandoMySQLDBA的帖子到这个dba.stackexchange question我觉得我在关于触发存储过程的初始保留中得到了证实 . 但是,如果您确定在任何给定时间仅通过用户输入更改了几行,则应该可以将快速操作的过程组合在一起 .
但是,如果有许多用户并行工作,他们可能仍会互相锁定 . 我不知道这是否真的会发生,因为存储过程不会改变
details
表中的任何内容 . 如果有必要,你可以查看this page的想法 .Edit: TRIGGER
我刚刚将上一篇文章的SQLfiddle扩展为SQLfiddle with trigger,包含以下内容:
我还定义了另一个功能
计算给定
varchar
中的单词 . 这也可用于在我的第一篇文章中为基本转换创建循环而不是我固定的UNION
构造 .触发器现在负责所有新的
INSERT
. 仍然需要编写类似的触发器来为UPDATE
做同样的事情 . 这不应该太难......在我的SQLfiddle中,我在触发器定义之后将另一行插入
detail
. 结果由两个比较SELECT语句列出,请参见fiddle .reply to last comment :
好吧,正如我在原始答案中建议的那样,你应该首先导入所有数据(没有安装任何触发器!!!!),然后使用
SELECT/UNION
语句浏览detail
-table . 在您这样做之前,您应该通过使用找出每个列assignee
,inventor
和ipsubclass
中的最大单词数然后,您可以调整每列所需的
SELECT/UNION
个数量 . 然后填写链接表,如SQLfiddle中所示 .也许整个过程需要一段时间,但您可以安全地在一个接一个的表上工作(首先是实际的属性表,然后是相关的链接表) .
之后,您可以激活触发器,然后应该只对单独添加的行进行操作 .
首先,在我看来,你应该将你的表分成四个独立的表:
detail
(主表,包含:id, projectid, publicationnumber, prioritycountry, prioritydate
和status
)inv
(发明人表,包含:invid, invname
,可能还有更多与发明人有关的数据)cls
(ipsubclass表,包含:clsid, clsname
,可能还有每个类的描述)assignee
(包含受让人公司的数据,如:assid, assname ...
)由于主表与
inv, cls
和assignee
之间将存在n:m
关系,因此您还应设置包含关系的链接表det2inv
det2cls
det2ass
重组任务可以分为几个步骤:
首先,您需要应用用户定义的函数来拆分组合值 . 您可以使用here描述的功能执行此操作
我进一步简化了它,因为在你的例子中我们只遇到一个单独的分隔符
|
:(注意
TRIM
功能可以砍掉任何东西不必要的空白...)接下来,您应该定义包含您的发明者和可能的ipsubclasses的目标表(......和受让人,我还没有完成):
您可以根据需要随意扩展带有其他列的表格 .
现在我们用唯一值填充表格 . 首先是表_1754820中的发明者:
接下来是ipsubclasses:
在我的例子中,我只查看了每个字段的前8个条目 . 这可以根据您的需要进行修改 . 您将得到两个唯一编号的表,其中包含所有可能的发明者和所有可能的子类(并且以类似的方式所有受让人) . 您可以在这里查看我的SQLfiddle:http://sqlfiddle.com/#!2/aeafe/1
剩下的任务现在是用合适的键填充链接表(主表
details
及其属性表inv, cls
和assignee
中的id对 .Edit
链接表填充了以下语句:
INSTR()
函数将无法正常工作,因为Hagen, Pete
等名称将与Gleichenhagen, Peter
成功匹配 . 为避免这些情况,应修改比较,如下所示:你可以在这里看到 complete working example :http://sqlfiddle.com/#!2/097be/8