我想知道在将表拆分为多对多关系时如何最好地迁移数据。我已经做了一个简化的例子,我还会发布一些我想出的解决方案。 我正在使用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
表PersonPet
FK_Person FK_Pet
1 6
2 7
3 8
4 9
注意:
我的解决方案
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;
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;
问题
答案 0 :(得分:5)
实现您所描述效果的另一种解决方案(在我看来是最简单的一种;没有任何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));
答案 1 :(得分:3)
是的,您的两种解决方案都是正确的。他们让我想起了this answer。
很少注意到。
在PersonID
表中添加额外列Pet
的第一个变体可以使用RETURNING
子句在单个查询中完成。
-- 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中RETURNING
中的INSERT
子句仅限于从目标表返回列,即只有那些实际插入的值。例如,在MS SQL Server中MERGE
可以从源表和目标表返回值,这使得这类任务变得简单,但我在Postgres中找不到任何类似的东西。
因此,在PersonID
表格中添加明确Pet
列的第二个变体需要将原始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
为空时,第二种变体可以正常工作,但如果您已经迁移了一批行,则过滤所需的行可能会变得很棘手。
答案 2 :(得分:3)
您可以通过首先插入外键表然后插入宠物表来克服必须向宠物表添加额外列的限制。这允许首先确定映射的内容,然后在第二遍中填写详细信息。
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:
我概述的解决方案的计算效率低于解决方案#1,因为它需要连接,但比解决方案#2更有效。