规范化具有冗余主键的MySQL表

时间:2014-06-16 12:32:52

标签: mysql normalization data-migration

我正在努力升级MySQL数据库的架构,该数据库存储人员及其相关的慈善机构列表。问题表Persons中包含个人数据和相关慈善机构。它包含以下字段:

  1. Person_Id (自动增加主键)
  2. Citizen_Id (一个独特的字母数字国家ID)
  3. Person_Full_Name (不言自明)
  4. Person_Email (不言自明)
  5. Person_Assistant_Contact (此人的个人助理手机)
  6. Charity_Org_Id (慈善组织主表的外键)
  7. Designation_Id (此人可以在慈善组织中持有的主指定表的外键)
  8. 如果需要,以下是上述DDL:

    CREATE TABLE `Persons` (
        `Person_Id` SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        `Citizen_Id` CHAR(10) NOT NULL UNIQUE COMMENT 'a unique alphanumeric national id',
        `Person_Full_Name` CHAR(100) NOT NULL,
        `Person_Email` CHAR(50) NULL,
        `Person_Assistant_Contact` CHAR(20) NULL COMMENT 'the cellphone of the persons personal assistant',
        `Charity_Org_Id` SMALLINT UNSIGNED NOT NULL COMMENT 'foreign key for a master table of charitable organizations',
        `Designation_Id` SMALLINT UNSIGNED NULL COMMENT 'foreign key for a master table of designations that the person can hold in the charitable organization',
        FOREIGN KEY (`Charity_Org_Id`) REFERENCES `Charity_Orgs` (`Charity_Org_Id`),
        FOREIGN KEY (`Designation_Id`) REFERENCES `Designations` (`Designation_Id`),    
    ) ENGINE = INNODB;
    

    由于有许多人参与多个慈善组织,因此该表有许多条目,其中名称和联系信息相同,但Charity_Org_Id和Designation_Id不同。 INSERT查询示例(请注意第一列和最后两列):

    INSERT INTO `Persons` VALUES
    (2387,'OZN13445','Frederick Oznawa','info@oznawaind.org','+54-332887789',128,12),
    (4533,'OZN13445','Frederick Oznawa','info@oznawaind.org','+54-332887789',520,2),
    (4555,'OZN13445','Frederick Oznawa','info@oznawaind.org','+54-332887789',522,4);
    

    显而易见的一步是将联系信息和相关的慈善机构分成不同的表格。这就是我想要的:

    INSERT INTO `Persons` VALUES
    (2387,'OZN13445','Frederick Oznawa','info@oznawaind.org','+54-332887789');
    
    INSERT INTO `Person_Charities` VALUES
    (2387,128,12),
    (2387,520,2),
    (2387,522,4);
    

    获取新的Persons表非常简单。 我希望有一个纯粹的SQL解决方案来获取' Person_Charities'表(我想使用每人最低的Person_Id并丢弃剩余的ID)。我可以使用电子表格软件手动执行此操作,但这很容易出错,我正在查看数千行。

    注意:我不想将Citizen_Id用作替代主键

1 个答案:

答案 0 :(得分:1)

您可以使用以下查询查询要分隔的列的不同列表:

SELECT
    MIN(`Person_Id`) AS Person_Id
    `Citizen_Id`,
    `Person_Full_Name`,
    `Person_Email`,
    `Person_Assistant_Contact`
FROM
    Persons
GROUP BY
    `Citizen_Id`,
    `Person_Full_Name`,
    `Person_Email`,
    `Person_Assistant_Contact`assistant'

好的,我们有一个查询来获取您桌子上的人员的明确列表。

创建新表格

您可以使用CREATE TABLE ... SELECT语法创建表,也可以创建一个新表来存储数据,并使用INSERT ... SELECT语法根据上述查询插入不同的人。我个人更喜欢手动构建一个新表。

CREATE TABLE `New_Persons` (
  `Person_Id` SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `Citizen_Id` CHAR(10) NOT NULL UNIQUE COMMENT 'a unique alphanumeric national id',
  `Person_Full_Name` CHAR(100) NOT NULL,
  `Person_Email` CHAR(50) NULL,
  `Person_Assistant_Contact` CHAR(20) NULL COMMENT 'the cellphone of the persons personal assistant'
);

将数据插入我们的全新表格

INSERT INTO New_Persons (
  Person_Id,
  Citizen_Id,
  Person_Full_Name,
  Person_Email,
  Person_Assistant_Contact
)
SELECT
    MIN(Person_Id) AS Person_Id,
    Citizen_Id,
    Person_Full_Name,
    Person_Email,
    Person_Assistant_Contact
FROM
    Persons
GROUP BY
    Citizen_Id,
    Person_Full_Name,
    Person_Email,
    Person_Assistant_Contact;

很好,我们有一张不同人士的桌子。如果您愿意,可以添加其他约束和索引。

如何继续?

我们有一个表格,其中包含不同的人(即:New_Persons)和一张表格,其中包含原始Persons表格中的(几乎)相似人物数据。下一步是减少旧的Persons表以仅存储所需的信息。

选项包括:

  • 添加新列以存储New_Persons表中的ID,然后使用Multiple-table update查询更新该列,然后删除不必要的列。
  • 创建一个新表来存储计算出的信息,然后删除旧表并重命名新表。

如果您有足够的存储空间,我建议您使用第二个选项。您可以验证插入的数据是否正常,并且您不需要完全重新组织旧表。最重要的是,丢失任何数据的机会要少得多(因为你没有触及现有的表,所以你可以在出现问题时重新启动这个过程。)

创建新表格

CREATE TABLE `Person_Charities` (
  `Person_Id` SMALLINT UNSIGNED,
  `Charity_Org_Id` SMALLINT UNSIGNED NOT NULL COMMENT 'foreign key for a master table of charitable organizations',
  `Designation_Id` SMALLINT UNSIGNED NULL COMMENT 'foreign key for a master table of designations that the person can hold in the charitable organization',
  PRIMARY KEY PK_PersonCharities (Person_Id, Charity_Org_Id)
);

再次,你可以调整键,索引和你想要的任何东西。

将数据插入新表格

所以,我们有一个新表是空的,两个旧表有一些数据,我们知道它们之间的关系。我们现在的目标是创建一个查询,以从旧的Persons表中检索新的Person_id和剩余列。

这听起来像使用除Person_Id

之外的公共列的给定表上的简单连接
INSERT INTO Person_Charities
(
  Person_Id,
  Charity_Org_Id,
  Designation_Id
)
SELECT
  NP.Person_Id,
  P.Charity_Org_Id,
  P.Designation_Id
FROM
  Persons P
  INNER JOIN New_Persons NP
    ON P.Citizen_Id = NP.Citizen_Id
      AND P.Person_Full_Name = NP.Person_Full_Name
      AND P.Person_Email = NP.Person_Email
      AND P.Person_Assistant_Contact = NP.Person_Assistant_Contact;

差不多完成了

很好,我们有新的表格,我们想要的数据,只是他们的名字令人困惑:)

重命名表格,我们完成了:

RENAME TABLE
  Persons TO Old_Persons,
  New_Persons TO Persons

<强>图片的标题说明

  • 在原始shema Citizen_Id中是唯一的,但您正在插入重复项。架构或插入的数据都是错误的。
  • 您应该检查新表中的数据,并在删除旧表之前验证没有出错。 (请记住,旧表的名称现在是Old_Persons!)
  • 在开始修改架构之前,始终备份数据库并测试备份文件(有一个叫墨菲的人你知道)
  • 如果New_Persons表中的任何列可以为空,则应修改最后一个连接。您必须处理NULL'值'
  • 如果有更多规范化,请考虑创建新数据库并将数据从旧数据库迁移到新数据库。

个人信息

我更喜欢创建具有所需结构和约束的全新表,并使用INSERT INTO ... SELECT语句填充它们。填充新表(并验证数据)后,重命名旧表,然后重命名新表。最后一步是将索引添加到新表中。一切正常后,旧表可以存档。

我希望,这有帮助。快乐的编码:)

哦,还有SQL Fiddle demo