【问题标题】:Mysql Stored Procedure to copy records in a Parent/Child/Grandchild hierarchyMysql存储过程复制父/子/孙层次结构中的记录
【发布时间】:2011-08-07 21:35:00
【问题描述】:

我有 3 个表格的父、子和孙子表:

+----------------+   +----------------+   +---------------------+
| parent         |   | child          |   | grandchild          |
+----------------+   +----------------+   +---------------------+
| parent_id (PK) |   | child_id (PK)  |   | grandchild_id (PK)  |
| parent_data    |   | child_data     |   | grandchild_data     |
+----------------+   | parent_id (FK) |   | child_id (FK)       |
                     +----------------+   +---------------------+ 

PK = 自动递增主键。
FK = 外键。

我想要一个可以复制父表中的记录以及子表和孙表中的任何关联记录的存储过程。我可以正常复制父子数据,这是我正在努力解决的孙子表。这是据我所知:

CREATE FUNCTION sf_copy_parent(p_parent_id INT) RETURNS INT
BEGIN
    DECLARE new_parent_id INT;

    -- create new parent record
    INSERT INTO parent(parent_data)
        SELECT parent_data FROM parent
        WHERE parent_id=p_parent_id;
    SET new_parent_id=LAST_INSERT_ID();

    -- copy child records
    INSERT INTO child(child_data,parent_id)
        SELECT child_data,new_parent_id FROM child
        WHERE parent_id=p_parent_id;

    -- copy grandchild records ???


    -- return
    RETURN new_parent_id;
END

如果这很重要,我正在使用 Mysql5.5。

【问题讨论】:

    标签: mysql stored-procedures


    【解决方案1】:

    试试这个 SELECT 查询(它使用 'p_parent_id' 和 'new_parent_id' 变量) -

    SET @r1 = 1;
    SET @child_id = NULL;
    SET @r2 = 0;
    
    SELECT c1.grandchild_data, c2.child_id FROM (
      SELECT @r1 := if(c.child_id IS NULL OR c.child_id <> @child_id, @r1 + 1, @r1) rank, @child_id := c.child_id, c.child_id, g.grandchild_data FROM child c
      JOIN grandchild g
        ON c.child_id = g.child_id
      WHERE
        c.parent_id = p_parent_id
      ORDER BY
        c.child_id, g.grandchild_id
      ) c1
    JOIN (SELECT @r2 := @r2 + 1 rank, child_id FROM child WHERE parent_id = new_parent_id ORDER BY child_id) c2
      ON c1.rank = c2.rank;
    

    如果可行,我们会将其重写为 INSERT..SELECT 语句,或者自己尝试;)

    【讨论】:

    • 完美运行,尽管我需要一段时间才能完全理解它!我现在拥有的完整存储过程,使用您的代码,我将在下面添加另一个答案。
    • 问题是——如何绑定两个子表来获取新的child_id。我试图通过它们的位置编号(按等级字段)绑定行。
    【解决方案2】:

    从那以后我学到了更多,我相信@Devart 的贡献可以简化一点。我现在使用的解决方案如下所示。

    注意:此解决方案仅在使用的表是 InnoDB 而不是 MyISAM 时才能正常工作。我认为这与 MyISAM 的默认排序行为的工作方式有关。 (InnoDB 使用主键,而 MyISAM 使用插入顺序。)特别是,对于 MyISAM 表,在“复制孙子”部分中生成的两组等级彼此不“同步”。在相关部分添加 SORT BY 子句似乎也没有什么不同。

    CREATE PROCEDURE sp_copy(p_parent_id INT)
    BEGIN
        DECLARE new_parent_id INT;
    
        -- copy parent
        INSERT INTO parent(parent_data) SELECT parent_data FROM parent WHERE parent_id=p_parent_id;
        SET new_parent_id:=LAST_INSERT_ID();
    
        -- copy child(s)
        INSERT INTO child(child_data, parent_id)
            SELECT child_data, new_parent_id FROM child WHERE parent_id=p_parent_id;
    
        -- copy grandchild(s)
        SET @rank1:=0;        
        SET @rank2:=0;            
        INSERT INTO grandchild(grandchild_data, child_id) SELECT gc.grandchild_data, c2.child_id FROM
            (SELECT child_id, @rank1:=@rank1+1 as rank FROM child WHERE parent_id=p_parent_id) c1
            INNER JOIN
            (SELECT child_id, @rank2:=@rank2+1 as rank FROM child WHERE parent_id=new_parent_id) c2 ON c1.rank=c2.rank
            INNER JOIN grandchild gc ON c1.child_id=gc.child_id;
    
    END
    

    此外,为了处理曾孙表,可以使用复制孙记录的相同原则。唯一的额外复杂性是在两个子查询中的每一个中添加一个连接。这是必需的,因为 child.parent_id 字段需要在 WHERE 子句中引用:

    -- copy greatgrandchild(s)
    SET @rank1:=0;        
    SET @rank2:=0;            
    INSERT INTO greatgrandchild(greatgrandchild_data, grandchild_id) SELECT ggc.greatgrandchild_data, gc2.grandchild_id FROM
        (SELECT grandchild_id, @rank1:=@rank1+1 as rank FROM grandchild INNER JOIN child ON child.child_id=grandchild.child_id WHERE parent_id=p_parent_id) gc1
        INNER JOIN
        (SELECT grandchild_id, @rank2:=@rank2+1 as rank FROM grandchild INNER JOIN child ON child.child_id=grandchild.child_id WHERE parent_id=new_parent_id) gc2 ON gc1.rank=gc2.rank
        INNER JOIN greatgrandchild ggc ON gc1.grandchild_id=ggc.grandchild_id;
    

    【讨论】:

      【解决方案3】:

      编辑:我已经创建了另一个我认为比这个更简单和更好的答案。

      然后使用@Devart 的答案完成的存储过程是:

      CREATE FUNCTION `sp_copy`(p_parent_id INT) RETURNS int(11)
      BEGIN
          DECLARE new_parent_id INT;
      
          -- create new parent record
          INSERT INTO parent(parent_data)
              SELECT parent_data FROM parent
              WHERE parent_id=p_parent_id;
          SET new_parent_id=LAST_INSERT_ID();
      
          -- copy child records
          INSERT INTO child(child_data,parent_id)
              SELECT child_data,new_parent_id FROM child
              WHERE parent_id=p_parent_id;
      
          -- copy grandchild records
          SET @r1 = 1;
          SET @child_id = NULL;
          SET @r2 = 0;
          INSERT INTO grandchild(grandchild_data,child_id) SELECT c1.grandchild_data, c2.child_id FROM (
          SELECT @r1 := if(c.child_id IS NULL OR c.child_id <> @child_id, @r1 + 1, @r1) rank, @child_id := c.child_id, c.child_id, g.grandchild_data FROM child c
          JOIN grandchild g
              ON c.child_id = g.child_id
          WHERE
              c.parent_id = p_parent_id
          ORDER BY
              c.child_id, g.grandchild_id
          ) c1
          JOIN (SELECT @r2 := @r2 + 1 rank, child_id FROM child WHERE parent_id = new_parent_id ORDER BY child_id) c2
              ON c1.rank = c2.rank;
      
          -- return new parent id
          RETURN new_parent_id;
      END
      

      【讨论】:

      • @Devart 您认为可以将其扩大到包括曾孙表吗?
      • 我认为它可以以某种方式完成。但现在 - 这是一个问题;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多