【问题标题】:Split Table into many to many relationship: Data Migration将表拆分为多对多关系:数据迁移
【发布时间】:2016-01-14 23:09:16
【问题描述】:

我想知道在将表拆分为多对多关系时如何最好地迁移我的数据。我做了一个简化的例子,我也会发布一些我想出的解决方案。 我正在使用 Postgresql 数据库。

迁移前

桌人


ID       Name        Pet        PetName
1        Follett     Cat        Garfield
2        Rowling     Hamster    Furry
3        Martin      Cat        Tom
4        Cage        Cat        Tom

迁移后

桌人


ID       Name
1        Follett
2        Rowling
3        Martin
4        Cage

餐桌宠物


ID       Pet        PetName
6        Cat        Garfield
7        Hamster    Furry
8        Cat        Tom
9        Cat        Tom

桌人宠物


FK_Person     FK_Pet
1             6
2             7
3             8
4             9

注意事项:

  • 我将专门复制 Pet Table 中的条目(因为在我的情况下 - 由于其他相关数据 - 其中一个可能仍可供客户编辑,而另一个可能不能)。
  • 没有唯一标识“宠物”记录的列。
  • 对我来说,3-8 和 4-9 是在 PersonPet 表中链接还是在 3-9 和 4-8 中链接并不重要。
  • 此外,我省略了处理表架构更改的所有代码,因为在我的理解中,这与这个问题无关。

我的解决方案

  1. 在创建 Pet Table 时临时添加一列,其中包含用于创建此条目的 Person Table 的 ID。

    ALTER TABLE Pet ADD COLUMN IdPerson INTEGER;

    INSERT INTO Pet (Pet, PetName, IdPerson)
    SELECT Pet, PetName, ID
    FROM Person;

    INSERT INTO PersonPet (FK_Person, FK_Pet)
    SELECT ID, IdPerson
    FROM Pet;

    ALTER TABLE Pet DROP Column IdPerson;
  1. 避免临时修改 Pet 表

    INSERT INTO Pet (Pet, PetName)
    SELECT Pet, PetName
    FROM Person;

    WITH
      CTE_Person
      AS
      (SELECT
        Id, Pet, PetName
        ,ROW_NUMBER() OVER (PARTITION BY Pet, PetName ORDER BY Id) AS row_number
      FROM Person
      )
      ,CTE_Pet
      AS
      (SELECT
        Id, Pet, PetName
        ,ROW_NUMBER() OVER (PARTITION BY Pet, PetName ORDER BY Id) AS row_number
      FROM Pet
      )
      ,CTE_Joined
      AS
      (SELECT
        CTE_Person.Id AS Person_Id,
        CTE_Pet.Id AS Pet_Id
      FROM
        CTE_Person
        INNER JOIN CTE_Pet ON
        CTE_Person.Pet = CTE_Pet.Pet
        CTE_Person.PetName = CTE_Pet.PetName
        AND CTE_Person.row_number = CTE_Pet.row_number
      )
      INSERT INTO PersonPet (FK_Person, FK_Pet)
      SELECT Person_Id, Pet_Id from CTE_Joined;

问题

  1. 两种解决方案都正确吗? (我已经测试了第二种解决方案,结果似乎是正确的,但我可能错过了一些极端情况)
  2. 这两种解决方案的优缺点是什么?
  3. 是否有更简单的方法来执行相同的数据迁移? (出于我的好奇心,我也会对稍微修改我的约束的答案感兴趣(例如 Pet 表中没有重复的条目),但请指出哪些 :))。

【问题讨论】:

    标签: sql postgresql many-to-many database-migration


    【解决方案1】:

    实现您描述的效果的另一种解决方案(我认为最简单的一种;没有任何 CTE-s 或其他列):

    create table Pet as
        select
            Id,
            Pet,
            PetName
        from 
            Person;
    
    create table PersonPet as
        select
            Id as FK_Person,
            Id as FK_Pet
        from
            Person;
    
    create sequence PetSeq;
    update PersonPet set FK_Pet=nextval('PetSeq'::regclass);
    update Pet p set Id=FK_Pet from PersonPet pp where p.Id=pp.FK_Person;
    
    alter table Pet alter column Id set default nextval('PetSeq'::regclass);
    alter table Pet add constraint PK_Pet primary key (Id);
    alter table PersonPet add constraint FK_Pet foreign key (FK_Pet) references Pet(Id);
    

    除非我们使用序列生成一个,否则我们只是使用现有的人员 ID 作为宠物的临时 ID。

    编辑

    也可以使用我已经完成架构更改的方法:

    insert into Pet(Id, Pet, PetName)
        select
            Id,
            Pet,
            PetName
        from
            Person;
    
    insert into PersonPet(FK_Person, FK_Pet)
        select
            Id,
            Id
        from
            Person;
    
    select setval('PetSeq'::regclass, (select max(Id) from Person));
    

    【讨论】:

    • 嗨。我喜欢这个解决方案,它看起来真的很整洁!在我的情况下,这不是一个真正的选择,因为对架构的更改是在不同的地方处理的。
    • 我编辑了我的帖子,并为架构已修改的情况提供了解决方案。基本上我们所要做的就是调整宠物的顺序,瞧:)。
    【解决方案2】:

    您可以通过先插入外键表然后再插入 pets 表来克服必须向 pets 表中添加额外列的限制。这允许首先确定映射是什么,然后在第二遍中填写详细信息。

    INSERT INTO PersonPet
    SELECT ID, nextval('pet_id_seq'::regclass) as PetID
    FROM Person;
    
    INSERT INTO Pet
    SELECT FK_Pet, Pet, Petname
    FROM Person join PersonPet on (ID=FK_Person);
    

    这可以使用 Vladimir 在他的回答中概述的公用表表达式机制组合成一个语句:

    WITH
    fkeys AS
    (
      INSERT INTO PersonPet
        SELECT ID, nextval('pet_id_seq'::regclass) as PetID
        FROM Person
      RETURNING FK_Person as PersonID, FK_Pet as PetID
    )
    INSERT INTO Pet
    SELECT f.PetID, p.Pet, p.Petname
    FROM Person p join fkeys f on (p.ID=f.PersonID);
    

    就优缺点而言:

    您的解决方案 #1:

    • 计算效率更高,它由两个扫描操作组成,没有连接和排序。
    • 空间效率较低,因为它需要在 Pet 表中存储额外数据。在 Postgres 中,DROP 列上的空间没有恢复(但您可以使用 CREATE TABLE AS / DROP TABLE 恢复它)。
    • 如果您重复执行此操作可能会导致问题,例如定期添加/删除列,因为您会遇到 Postgres 最大列限制。

    我概述的解决方案的计算效率低于您的解决方案 #1,因为它需要连接,但比您的解决方案 #2 更有效。

    【讨论】:

      【解决方案3】:

      是的,您的两个解决方案都是正确的。他们让我想起了this answer

      一些注释。

      Pet 表中添加额外列PersonID 的第一个变体可以使用RETURNING 子句在单个查询中完成。

      SQL Fiddle

      -- Add temporary PersonID column to Pet
      
      WITH
      CTE_Pets
      AS
      (
          INSERT INTO Pet (PersonID, Pet, PetName)
          SELECT Person.ID, Person.Pet, Person.PetName
          FROM Person
          RETURNING ID AS PetID, PersonID
      )
      INSERT INTO PersonPet (FK_Person, FK_Pet)
      SELECT PersonID, PetID
      FROM CTE_Pets
      ;
      
      -- Drop temporary PersonID column
      

      不幸的是,Postgres 中INSERT 中的RETURNING 子句似乎仅限于仅从目标表返回列,即仅返回实际插入的那些值。例如,在 MS SQL Server 中,MERGE 可以从源表和目标表中返回值,从而使此类任务变得容易,但我在 Postgres 中找不到类似的东西。

      因此,第二个变体没有在Pet 表中添加显式PersonID 列,需要将原始Person 与新Pet 连接起来,以将旧PersonID 映射到新PetID

      如果您的示例中可能存在重复 (Cat Tom),请使用 ROW_NUMBER 分配序列号以区分重复行,如您在问题中所示。

      如果没有这样的重复,那么你可以简化映射,去掉ROW_NUMBER

      INSERT INTO Pet (Pet, PetName)
      SELECT Pet, PetName
      FROM Person;
      
      INSERT INTO PersonPet (FK_Person, FK_Pet)
      SELECT
          Person.ID AS FK_Person
          ,Pet.ID AS FK_Pet
      FROM
          Person
          INNER JOIN Pet ON
              Person.Pet = Pet.Pet AND
              Person.PetName = Pet.PetName
      ;
      

      我看到了第一种方法的一个优点。

      如果您将PersonID 显式存储在Pet 表中,则分几个步骤分批执行这种迁移会更容易。当PersonPet 为空时,第二个变体可以正常工作,但如果您已经迁移了一批行,那么过滤所需的行可能会变得很棘手。

      【讨论】:

        猜你喜欢
        • 2013-03-23
        • 2021-12-27
        • 1970-01-01
        • 2012-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-12
        相关资源
        最近更新 更多