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

时间:2013-09-02 12:48:58

标签: mysql sql stored-procedures split database-normalization

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

我的挑战是使用分隔符assignee, inventor and ipcsubclass将列|拆分为新表{detail_invinventors},{detail_asg和{{1} }和{assignees以及detail_ipc}。

在所有三种情况下,表模式都类似。例如,发明人列表中的列 - ipcsubclassesid以及detail_inv表 - namedetail_id。每行只能有一个名称,发明人表中的所有名称都是唯一的,而在详细信息表中,ID必须保持关系。

我尝试使用下面的代码存储过程给发明者 - 我为3列做了3个程序:(

inventor_id

当我在一些随机测试数据上尝试这个时,它工作正常。当我在实际桌子上这样做时,它不能很好地工作。仅插入部分数据。 SQL不会抛出任何错误(有些时候除外:“#1172 - 结果由多行组成”或“inventor_id列不能为空”)

我尝试修改代码 MySQL - Insert comma separated list into normalized tables via stored procedure以满足我的需求,但我失败了。

请帮助我,我的数据库表变得一团糟,大约有500,000行,这使我很难在每个项目上爆炸和管理大型数组(最近的项目有大约200,000行)。

2 个答案:

答案 0 :(得分:3)

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

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

编辑: 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

回复上次评论

好吧,正如我在原始回答中建议的那样,您应首先导入所有数据(不安装任何触发器!!!!)然后翻阅detail - 包含SELECT/UNION语句的表。在您执行此操作之前,您应该使用

找出每个列assigneeinventoripsubclass中的最大单词数
SELECT MAX(cntparts(inventor)) invcnt,
       MAX(cntparts(assignee)) asscnt,
       MAX(cntparts(ipsubclass)) clscnt 
FROM detail

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

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

之后,您可以激活应该的触发器,然后只能在单独添加的行上工作。

答案 1 :(得分:2)

首先,在我看来,你应该将你的桌子划分为四个独立的桌子:

  1. detail(主要表格,包含:id, projectid, publicationnumber, prioritycountry, prioritydatestatus
  2. inv (发明人表,包含:invid, invname,可能还有更多 发明者相关数据)
  3. cls(ipsubclass表,包含:clsid, clsname,可能还有每个类的描述)
  4. assignee(包含受让人公司的数据,如:assid, assname ...
  5. 由于主表与n:minv, cls之间存在assignee关系,因此您还应设置包含

    等关系的链接表
    • 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));
    

    随意扩展带有其他列的表,就像您需要的那样。

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

    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对。

    修改

    链接表正在填写以下语句:

    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;
    

    您可以在此处看到完整的工作示例http://sqlfiddle.com/#!2/097be/8