首页 文章

MySQL:存储过程拆分列(带分隔符)并插入新表

提问于
浏览
1

我的数据库中有一个未规范化的表,其名称为 details 结构,示例数据如下所示(图像道歉,只是认为它更容易理解):
Schema

我的挑战是使用分隔符 | 将列分割为 assignee, inventor and ipcsubclass 到新表{ detail_invinventors },{ detail_asgassignees }以及{ detail_ipcipcsubclasses } .

在所有三种情况下,表模式都类似 . 例如,发明人表_1154776_和 name 以及detail_inv表 detail_idinventor_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 回答

  • 3

    看看RolandoMySQLDBA的帖子到这个dba.stackexchange question我觉得我在关于触发存储过程的初始保留中得到了证实 . 但是,如果您确定在任何给定时间仅通过用户输入更改了几行,则应该可以将快速操作的过程组合在一起 .

    但是,如果有许多用户并行工作,他们可能仍会互相锁定 . 我不知道这是否真的会发生,因为存储过程不会改变 details 表中的任何内容 . 如果有必要,你可以查看this page的想法 .

    Edit: TRIGGER

    我刚刚将上一篇文章的SQLfiddle扩展为SQLfiddle with trigger,包含以下内容:

    CREATE TRIGGER normdet AFTER INSERT ON detail FOR EACH ROW
    BEGIN
      DECLARE n int; DECLARE word VARCHAR(64)
    
     ;SET n=cntparts(NEW.inventor)
     ;WHILE n>0 DO
       SET word=part(new.inventor,n)
       ;IF NOT EXISTS (SELECT * FROM inv WHERE invname=word) THEN
         INSERT INTO inv (invname) VALUES (word)
       ;END IF
       ;INSERT INTO det2inv (didid,diiid) 
        SELECT NEW.id,invid FROM inv WHERE invname=word
       ;SET n=n-1
     ;END WHILE
      -- and similar loops for assignee and cls ...
    ;END;
    

    我还定义了另一个功能

    CREATE FUNCTION cntparts (var varchar(1024)) RETURNS int
    RETURN 1+LENGTH(var)-LENGTH(REPLACE(var,'|',''));
    

    计算给定 varchar 中的单词 . 这也可用于在我的第一篇文章中为基本转换创建循环而不是我固定的 UNION 构造 .

    触发器现在负责所有新的 INSERT . 仍然需要编写类似的触发器来为 UPDATE 做同样的事情 . 这不应该太难......

    在我的SQLfiddle中,我在触发器定义之后将另一行插入 detail . 结果由两个比较SELECT语句列出,请参见fiddle .

    reply to last comment

    好吧,正如我在原始答案中建议的那样,你应该首先导入所有数据(没有安装任何触发器!!!!),然后使用 SELECT/UNION 语句浏览 detail -table . 在您这样做之前,您应该通过使用找出每个列 assigneeinventoripsubclass 中的最大单词数

    SELECT MAX(cntparts(inventor)) invcnt,
           MAX(cntparts(assignee)) asscnt,
           MAX(cntparts(ipsubclass)) clscnt 
    FROM detail
    

    然后,您可以调整每列所需的 SELECT/UNION 个数量 . 然后填写链接表,如SQLfiddle中所示 .

    也许整个过程需要一段时间,但您可以安全地在一个接一个的表上工作(首先是实际的属性表,然后是相关的链接表) .

    之后,您可以激活触发器,然后应该只对单独添加的行进行操作 .

  • 2

    首先,在我看来,你应该将你的表分成四个独立的表:

    • detail (主表,包含: id, projectid, publicationnumber, prioritycountry, prioritydatestatus

    • inv (发明人表,包含: invid, invname ,可能还有更多与发明人有关的数据)

    • cls (ipsubclass表,包含: clsid, clsname ,可能还有每个类的描述)

    • assignee (包含受让人公司的数据,如: assid, assname ...

    由于主表与 inv, clsassignee 之间将存在 n:m 关系,因此您还应设置包含关系的链接表

    • det2inv

    • det2cls

    • det2ass

    重组任务可以分为几个步骤:

    首先,您需要应用用户定义的函数来拆分组合值 . 您可以使用here描述的功能执行此操作

    我进一步简化了它,因为在你的例子中我们只遇到一个单独的分隔符 |

    CREATE FUNCTION part( x VARCHAR(255), pos INT) 
    RETURNS VARCHAR(255) BEGIN
     DECLARE delim char(1)
     ;SET delim='|'
     ;RETURN TRIM(REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
                       LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
           delim, ''))
     ;END;
    

    (注意 TRIM 功能可以砍掉任何东西不必要的空白...)

    接下来,您应该定义包含您的发明者和可能的ipsubclasses的目标表(......和受让人,我还没有完成):

    CREATE TABLE inv (invid int auto_increment PRIMARY KEY, invname nvarchar(64));
    CREATE TABLE cls (clsid int auto_increment PRIMARY KEY, clsname nvarchar(6));
    

    您可以根据需要随意扩展带有其他列的表格 .

    现在我们用唯一值填充表格 . 首先是表_1754820中的发明者:

    INSERT INTO inv (invname) 
    SELECT inv FROM (
     SELECT part(inventor,1) inv from detail
     UNION 
     SELECT part(inventor,2) from detail
     UNION 
     SELECT part(inventor,3) from detail
     UNION 
     SELECT part(inventor,4) from detail
     UNION 
     SELECT part(inventor,5) from detail
     UNION 
     SELECT part(inventor,6) from detail
     UNION 
     SELECT part(inventor,7) from detail
     UNION 
     SELECT part(inventor,8) from detail
    ) t WHERE inv>'' ORDER BY inv;
    

    接下来是ipsubclasses:

    INSERT INTO cls (clsname)
    SELECT icls FROM (
     SELECT part(iclass,1) icls from detail
     UNION 
     SELECT part(iclass,2) from detail
     UNION 
     SELECT part(iclass,3) from detail
     UNION 
     SELECT part(iclass,4) from detail
     UNION 
     SELECT part(iclass,5) from detail
     UNION 
     SELECT part(iclass,6) from detail
     UNION 
     SELECT part(iclass,7) from detail
     UNION 
     SELECT part(iclass,8) from detail
    ) t WHERE icls>'' ORDER BY icls;
    

    在我的例子中,我只查看了每个字段的前8个条目 . 这可以根据您的需要进行修改 . 您将得到两个唯一编号的表,其中包含所有可能的发明者和所有可能的子类(并且以类似的方式所有受让人) . 您可以在这里查看我的SQLfiddle:http://sqlfiddle.com/#!2/aeafe/1

    剩下的任务现在是用合适的键填充链接表(主表 details 及其属性表 inv, clsassignee 中的id对 .

    Edit

    链接表填充了以下语句:

    INSERT INTO det2inv (didid,diiid)
    SELECT id,invid FROM inv 
    INNER JOIN detail ON INSTR(inventor,invname)>0;
    
    INSERT INTO det2cls (dcdid,dccid)
    SELECT id,clsid FROM cls 
    INNER JOIN detail ON INSTR(iclass,clsname)>0;
    
    -- ... and a similar one for det2ass
    

    INSTR() 函数将无法正常工作,因为 Hagen, Pete 等名称将与 Gleichenhagen, Peter 成功匹配 . 为避免这些情况,应修改比较,如下所示:

    ...
    INNER JOIN detail ON INSTR(REPLACE(CONCAT('|',inventor,'|'),' ',''),
                               REPLACE(CONCAT('|',invname,'|'),' ',''))>0;
    

    你可以在这里看到 complete working examplehttp://sqlfiddle.com/#!2/097be/8

相关问题