使用Oracle SQL中的外键将旧表迁移到规范化的数据结构

时间:2019-03-04 19:49:15

标签: sql oracle filter

在重新构建数据库方面我遇到了一些麻烦。我有一个book数据库,其中仅包含一个表格,所有作者的数据都包含在每本书之后。我正在尝试重新制作该数据库,以具有一个author表和一个book表。

我使用以下命令制作了author表:

CREATE TABLE AUTHORS 
AS SELECT AUTHOR_NAME, AUTHOR_SURNAME, AUTHOR_BIRTHDATE

如果我现在想重新制作book表,如何添加外键,以便每本书的作者都是正确的?也就是说,如果原始book表上的第一项是:

ISBN1 Title1 Author_Name1 Author_Surname1 Author_Birthdate1

如何将这些数据导入新表,以便新作者字段(外键)引用author表中的正确条目?抱歉,这很令人困惑。

2 个答案:

答案 0 :(得分:1)

您正在寻找将现有表格分为两个表格,一个用于存储作者,另一个用于书籍。为了使其正常工作,您需要为每个作者创建一个唯一的ID。这是逐步的方法。


假定以下旧数据结构:

create table old_books (
    isbn             NUMBER(13, 0),
    title            VARCHAR2(200),
    author_name      VARCHAR2(200),
    author_surname   VARCHAR2(200),
    author_birthdate DATE
);

此示例数据:

         ISBN | TITLE  | AUTHOR_NAME | AUTHOR_SURNAME | AUTHOR_BIRTHDATE
------------: | :----- | :---------- | :------------- | :---------------
1000000000001 | book 1 | name 1      | surname 1      | 01-MAR-90       
1000000000002 | book 2 | name 2      | surname 2      | 01-MAR-95       
1000000000003 | book 3 | name 1      | surname 1      | 01-MAR-90       

首先,让我们为authors创建并馈送新的数据结构(请注意,您不想使用CREATE TABLE AS SELECT ...,因为这不允许您添加约束或其他有用的选项)。

要生成唯一的作者ID,我们使用IDENTITY功能(从Oracle 12c开始可用-如果没有此功能,则需要创建序列和触发器)。

在旧有数据中,我们假设每个作者均通过其姓名,姓氏和出生日期来唯一标识:

CREATE TABLE authors (
    id         NUMBER GENERATED ALWAYS AS IDENTITY,
    name       VARCHAR2(200),
    surname    VARCHAR2(200),
    birthdate  DATE,
    PRIMARY KEY (id)
);

INSERT INTO AUTHORS (name, surname, birthdate)
SELECT DISTINCT author_name, author_surname, author_birthdate FROM old_books;

2 rows affected

SELECT * FROM authors;

ID | NAME   | SURNAME   | BIRTHDATE
-: | :----- | :-------- | :--------
 1 | name 1 | surname 1 | 01-MAR-90
 2 | name 2 | surname 2 | 01-MAR-95

有了第一个表后,我们现在可以创建books表。它包含一个引用authors表的主键的外键。要填充表,我们需要将旧表与新的authors表连接起来以恢复每个作者的ID:

CREATE TABLE books (
    isbn       NUMBER(13, 0),
    title      VARCHAR2(200),
    author_id  NUMBER,
    CONSTRAINT book_author FOREIGN KEY(author_id) REFERENCES authors(id),
    PRIMARY KEY (isbn)
);

INSERT INTO books(isbn, title, author_id)
SELECT ob.isbn, ob.title, a.id
FROM old_books ob
INNER JOIN authors a 
    ON  a.name = ob.author_name
    AND a.surname = ob.author_surname
    AND a.birthdate = ob.author_birthdate;

3 rows affected

SELECT * FROM books;

         ISBN | TITLE  | AUTHOR_ID
------------: | :----- | --------:
1000000000001 | book 1 |         1
1000000000002 | book 2 |         2
1000000000003 | book 3 |         1

全部准备好!在适当的约束条件下,数据可以在两个表之间正确分配。我们可以通过查询将两个表连接起来:

SELECT b.isbn, b.title, a.name, a.surname, a.birthdate
FROM authors a
INNER JOIN books b ON a.id = b.author_id;

         ISBN | TITLE  | NAME   | SURNAME   | BIRTHDATE
------------: | :----- | :----- | :-------- | :--------
1000000000001 | book 1 | name 1 | surname 1 | 01-MAR-90
1000000000002 | book 2 | name 2 | surname 2 | 01-MAR-95
1000000000003 | book 3 | name 1 | surname 1 | 01-MAR-90

答案 1 :(得分:0)

您说作者的名字加姓氏是您作者表的主键。这是一种有效的方法。如果两位作者的名字相同,则必须找到“ John” +“ Smith”和“ John R”之类的解决方案。 +'Smith'或'John'+'Smith(幻想作者)'。这被称为自然复合密钥,尽管它不是完美的密钥,因为我们可能不得不处理提到的重复名称。另一方面,存在存在个同名作者,因此我们可能会立即遇到此问题;-)

书籍通过其ISBN进行标识,由于没有重复项,因此可以提供更好的自然键。 (仅当您要添加没有ISBN的非常古老的书籍或自售书籍时,才需要创建伪造的ISBN。)

为了使您的书引用作者,您必须包括完整的密钥,在此是第一位,也要姓。这不是多余的,因为这是在数据库中标识作者的关键。

CREATE TABLE books AS SELECT isbn, title, author_name, author_surname FROM old_table;
ALTER TABLE books ADD CONSTRAINT fk_book_author FOREIGN KEY (author_name, author_surname)
                                                REFERENCES authors (author_name, author_surname);

一种替代方法是引入代理(即技术)密钥。您将为每本书和每位作者生成一个ID(数字)并与他们合作。 (这意味着book表中将包含一个author_id。)但是对于一个好的数据库,您仍然应该考虑一下自然标识行的内容。这使以后编写查询的人更容易。 (例如,有人要求选择作者列表和他们写的书的数量。如何编写该查询?只显示名字和姓氏就足够了吗?或者我们可以在两行后面写上“ John Smith | 5”和“ John Smith | 2“,询问者说他们不能使用这个模棱两可的结果吗?)即使提供了代理密钥,您也应该对自然密钥有唯一的约束(如果有)。对于带有可选ISBN的图书,它可以是书名+ author_id,对于作者,它可以是名字+姓氏+出生日期。

顺便说一句:有些书的作者不止一个;-)