将数据插入强规范化数据库并保持完整性(Postgres)

Posted

技术标签:

【中文标题】将数据插入强规范化数据库并保持完整性(Postgres)【英文标题】:Insert data into strongly normalized DB and maintain the integrity (Postgres) 【发布时间】:2016-08-24 16:35:31 【问题描述】:

我正在尝试为电话簿开发一个简单的数据库。这是我写的:

CREATE TABLE phone
(
    phone_id SERIAL PRIMARY KEY,
    phone    CHAR(15),
    sub_id   INT,   -- subscriber id --
    cat_id   INT    -- category id --
);

CREATE TABLE category
(
    cat_id   SERIAL PRIMARY KEY,    -- category id --
    cat_name CHAR(15)       -- category name --
);

CREATE TABLE  subscriber
(
    sub_id  SERIAL PRIMARY KEY,
    name    CHAR(20),
    fname   CHAR(20),   -- first name --
    lname   CHAR(20),   -- last name --
);

CREATE TABLE address
(
    addr_id       SERIAL PRIMARY KEY,
    country       CHAR(20),
    city          CHAR(20),
    street        CHAR(20),
    house_num     INT,
    apartment_num INT
);

-- many-to-many relation --
CREATE TABLE sub_link
(
    sub_id   INT REFERENCES subscriber(sub_id),
    addr_id  INT
);

我为多对多关系创建了一个链接表,因为很少有人可以住在同一个地址,而一个人可以在不同的时间住在不同的地点。

但我不知道如何像这样在强规范化数据库中添加数据并保持数据的完整性。

第一个改进是我在地址表上添加了唯一键,因为该表不应包含重复数据:

CREATE TABLE address
(
    addr_id       SERIAL PRIMARY KEY,
    country       CHAR(20),
    city          CHAR(20),
    street        CHAR(20),
    house_num     INT,
    apartment_num INT,
    UNIQUE (country, city, street, house_num, apartment_num)
);

现在的问题是如何将关于某人的新记录添加到数据库中。我想我应该使用下一个动作顺序:

    subscriber 表中插入一条记录,因为sub_linkphone 表必须使用新订阅者的ID。

    address 表中插入一条记录,因为addr_id 在向sub_link 添加记录之前必须存在。

    链接sub_link 表中subscriberaddress 的最后记录。但是在这一步我有一个新问题:如何在PostgreSQL中有效地从步骤1)和2)中获取sub_idaddr_id

    然后我需要在phone表中插入一条记录。在 3) 步骤中,我不知道如何有效地从以前的查询中获取 sub_id

我在 Postgres 中读到了 WITH 块,但我不知道如何在我的情况下使用它。

更新 我已经完成了 ASL 建议的操作:

-- First record --
WITH t0 AS (
    WITH t1 AS (
            INSERT INTO subscriber
            VALUES(DEFAULT, 'Twilight Sparkle', NULL, NULL)
            RETURNING sub_id
    ),
    t2 AS (
            INSERT INTO address
            VALUES(DEFAULT, 'Equestria', 'Ponyville', NULL, NULL, NULL)
            RETURNING addr_id 
    )
    INSERT INTO sub_link
    VALUES((SELECT sub_id FROM t1), (SELECT addr_id FROM t2))
)
INSERT INTO phone
VALUES (DEFAULT, '000000', (SELECT sub_id FROM t1), 1);

但是我有一个错误:包含数据修改语句的WITH子句必须在顶层 第 2 行:使用 t1 AS (INSERT INTOsubscriber VALUES(DEFAULT,

【问题讨论】:

【参考方案1】:

您可以使用带有 RETURNING 子句的 WITH 块在一个查询中完成所有操作。见PostgreSQL docs on INSERT。例如:

WITH t1 AS (INSERT INTO subscriber VALUES ... RETURNING sub_id),
t2 AS (INSERT INTO address VALUES ... RETURNING addr_id)
INSERT INTO sub_link VALUES ((SELECT sub_id FROM t1), (SELECT addr_id FROM t2))

请注意,这种简单的表单仅在向每个表中插入一行时才有效。

这有点偏离您的问题的主题,但我建议您也考虑在电话表外键中创建 sub_id 和 cat_id 列(使用 REFERENCES)。

【讨论】:

postgres 是否允许在 WITH 块中执行两次测序插入?比如这样:INSERT INTO sub_link VALUES ((SELECT sub_id FROM t1), (SELECT addr_id FROM t2)), INSERT INTO phone VALUES (DEFAULT, "1111111", (SELECT sub_id FROM t1), ... 当然,只需将除最后一个 INSERT 之外的所有内容都放入其自己的 WITH 块中。每个 WITH 块都可以引用它之前的别名。例如。 ... t3 AS (INSERT INTO sub_link VALUES ((SELECT ... FROM t1), ...)) INSERT INTO phone ... 更新了原帖。查询变得非常复杂。使用嵌套的WITH 块我收到错误WITH clause containing a data-modifying statement must be at the top 不要嵌套您的 WITH 语句,只需将它们一个接一个地放置。 WITH t1 AS (INSERT INTO subscriber ...), t2 AS (INSERT INTO address ...), t3 AS (INSERT INTO sub_link ...) INSERT INTO phone ...【参考方案2】:

你明白了。从最顶层的表中插入数据,以便在插入对它们的引用之前获得它们的 ID。

在 PostgreSQL 中,您可以使用 INSERT/UPDATE ... RETURNING id 构造。如果您没有使用一些自动执行此操作的 ORM,这可能很有用。

这里唯一的事情是,在第 2 步中,您可能想在插入之前检查地址是否已经存在:

SELECT addr_id FROM address WHERE country = ? AND city = ? ...

【讨论】:

以上是关于将数据插入强规范化数据库并保持完整性(Postgres)的主要内容,如果未能解决你的问题,请参考以下文章

数据库使用规范

Flow学习笔记

腾讯云复盘用户数据丢失故障:存在人为不规范操作,将积极改进

MSSQL自增序列删除数据后如何保持完整

[学习笔记]--数据库系统

Redis缓存数据库