很多表都是一对一的关系

时间:2017-08-21 14:07:40

标签: sql database postgresql sqlalchemy

我是使用数据库的新手,我正在尝试设计一个新的数据库,我认为我需要在许多表中建立一对一的关系。

为了演示我的设计,我们假设我正在建立一个计划数据库作为示例。我首先为一个具有一对多关系的人创建一个表

Intent

接下来,我创建一个事件表,其中包含人际关系的许多部分

CREATE TABLE person (
    person_id SERIAL NOT NULL, 
    name VARCHAR,
    PRIMARY KEY (person_id)
);

现在假设我有两种不同类型的事件,它们有不同的信息,比如用餐和家庭作业

CREATE TABLE events (
    event_id SERIAL NOT NULL, 
    type VARCHAR,
    name VARCHAR,
    person_id INTEGER,
    time TIMESTAMP WITHOUT TIME ZONE,
    PRIMARY KEY (event_id),
    FOREIGN KEY(person_id) REFERENCES person (person_id)
);

现在,我尝试以这种方式设计数据库的原因是因为有时候,您可能只想显示每个人的基本事件列表,而不管该事件是什么。例如,如果我按如下方式初始化表格

CREATE TABLE meals (
    event_id INTEGER NOT NULL, 
    food VARCHAR,
    utensils VARCHAR,
    PRIMARY KEY (event_id),
    FOREIGN KEY(event_id) REFERENCES events (event_id)
);

CREATE TABLE homework (
    event_id INTEGER NOT NULL, 
    subject VARCHAR,
    completed BOOLEAN,
    score FLOAT,
    PRIMARY KEY (event_id),
    FOREIGN KEY(event_id) REFERENCES events (event_id)
);

然后我可能想要为Brad

生成所有事件的列表
INSERT INTO person (name) VALUES ('Brad');
INSERT INTO events (type, name, person_id, time) VALUES ('meal', 'lunch', 1, '12/28/2016 12:00:00')    
INSERT INTO events (type, name, person_id, time) VALUES ('meal', 'breakfast', 1, '12/28/2016 12:00:00');
INSERT INTO meals (event_id, food, utensils) VALUES (1, 'eggs', 'fork');
INSERT INTO meals (event_id, food, utensils) VALUES (2, 'turkey sandwich', 'hands');
INSERT INTO events (type, name, person_id, time) VALUES ('homework', 'final project', 1, '12/28/2016 18:00:00');
INSERT INTO homework (event_id, subject, completed, score) VALUES (3, 'Math', 'T', 0.93);

这很容易,我很困惑的是,如果我想看看Brad吃了什么怎么办。我想我可以在SELECT (events.time, events.type, events.name) FROM events LEFT JOIN person ON person.person_id = events.person_id WHERE person.name = 'Brad'; JOIN以及personevents之间使用两个events语句,但如果我只是想通过Brads事件和获取有关每个事件的所有额外信息(例如,如果事件是用餐,告诉我他吃了什么,如果是功课,告诉我他得到的分数)?

总的来说,我有几个问题。

  1. 这是一个很好的数据库设计还是我还应该考虑其他什么?上面的每个潜在用例,加上几个都是我将使用数据库所需的标准内容。
  2. 如何在事件表中的任何给定事件中轻松确定要查找的表格?这里有几点想法 - 我可以在事件表中存储包含有关事件的更多信息的另一个表的名称(即,用meals列替换type列)但我认为我在某个地方读到了一个坏主意。
  3. 其他一些说明,我正在使用Postgresql作为数据库。我所建立的实际数据库除了我在此处显示的内容外,还为每个表提供了更详细的信息。我只想弄清楚我想要达到的目的。最后,我正在使用sqlalchemy的ORM构建/访问数据库,所以如果有一个很好的技巧我可以使用table来帮助解决这个问题,这对我们来说也是非常有用的。

2 个答案:

答案 0 :(得分:1)

如果您想获取每个事件的所有详细信息,那么您将遇到问题,因为包含事件详细信息的表具有不同类型的列。并且您当然不希望在代码中硬编码各种事件详细信息表名,毕竟当您想要添加或删除表或更改名称时会发生什么?你必须到处更新你的代码!

首先,我要说你想要一个观点。类似的东西:

CREATE OR REPLACE VIEW event_details AS
    SELECT * FROM meals
    UNION ALL
    SELECT * FROM homework;

这样您就可以一次性选择所有事件类型的详细信息,例如

SELECT * FROM event_details WHERE event_id IN (
    SELECT event_id FROM events WHERE person_id = (
        SELECT person_id
        FROM person
        WHERE name = 'Brad'
    )
)

除了它当然不起作用,因为表结构不同。因此,您需要找到一种以统一的方式表示数据的方法;例如,在每条记录上执行ROW_TO_JSON

CREATE OR REPLACE VIEW event_details AS
    SELECT ROW_TO_JSON(meals.*) AS details FROM meals
    UNION ALL
    SELECT ROW_TO_JSON(homework.*) AS details FROM homework;

现在这个查询:

SELECT * FROM event_details WHERE (details->>'event_id')::INTEGER IN (
    SELECT event_id FROM events WHERE person_id = (
        SELECT person_id
        FROM person
        WHERE name = 'Brad'
    )
)

给你:

{"event_id":1,"food":"eggs","utensils":"fork"}
{"event_id":2,"food":"turkey sandwich","utensils":"hands"}
{"event_id":3,"subject":"Math","completed":true,"score":0.93}

然后你可以解析JSON并用它做你想做的事。当您想要添加,删除或重命名表时,只能在视图中执行。

现在请注意,我并不是说这是一种很好的(或唯一的)方法。我不清楚每种事件类型都有一个单独的表,而不是只有一个events表并将特定于类型的数据放在JSONB字段中。这将使查询更容易和更快,如果您使用JSONB,也可以索引特定于类型的数据。根据您展示的示例,我认为这将是一个更好的设计。

答案 1 :(得分:0)

所以@ eurotrash的回答回答了我问得很漂亮的问题,所以我接受了他的正确答案,但根据他的回答,我想出了我认为这个数据库更好的设计我想要的分享,以防其他人有类似的问题。基本上,我们将删除events表,而是创建一个物化视图来表示事件信息。首先,我们需要修改mealshomework表的设置方式,并包含以前在events表中的信息

CREATE TABLE meals (
    meal_id SERIAL NOT NULL, 
    name VARCHAR,
    person_id INTEGER,
    time TIMESTAMP WITHOUT TIME ZONE,
    food VARCHAR,
    utensils VARCHAR,
    PRIMARY KEY (meals_id),
    FOREIGN KEY(person_id) REFERENCES person (person_id)
);

CREATE TABLE homework (
    homework_id SERIAL NOT NULL, 
    name VARCHAR,
    person_id INTEGER,
    time TIMESTAMP WITHOUT TIME ZONE,
    subject VARCHAR,
    completed BOOLEAN,
    score FLOAT,
    PRIMARY KEY (homework_id),
    FOREIGN KEY(person_id) REFERENCES person (person_id)
);

现在,我们可以使用以下命令初始化数据库:

INSERT INTO person (name) VALUES ('Brad');
INSERT INTO meals (name, person_id, time, food, utensils) VALUES ('breakfast', 1, '12/28/2016 6:00:00', 'eggs', 'fork');
INSERT INTO meals (name, person_id, time, food, utensils) VALUES ('lunch', 1, '12/28/2016 12:00:00', 'turkey sandwich', 'hands');
INSERT INTO homework (name, person_id, time, subject, completed, score) VALUES ('final project', 1, '12/28/2016 18:00:00', 'Math', 'T', 0.93);

然后使用

创建公共信息的新材料视图
CREATE MATERIALIZED VIEW events AS 
SELECT meal_id as id, 'meals' as table, name, person_id, time FROM meals
UNION ALL
SELECT homework_id as id, 'homework' as table, name, person_id, time from homework;

给出了

 id |  table   |     name      | person_id |        time
----+----------+---------------+-----------+---------------------
  1 | meals    | breakfast     |         1 | 2016-12-28 06:00:00
  2 | meals    | lunch         |         1 | 2016-12-28 12:00:00
  1 | homework | final project |         1 | 2016-12-28 18:00:00

最后,为了确保events视图始终是最新的,我们可以创建触发器,以便在mealshomework根据https://stackoverflow.com/a/23963969/3431189 <更改时更新视图/ p>

CREATE OR REPLACE FUNCTION refresh_events_view()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
    REFRESH MATERIALIZED VIEW events;
    RETURN null;
end $$;

CREATE TRIGGER refresh_events_view
AFTER INSERT or UPDATE or DELETE or TRUNCATE
ON meals FOR EACH STATEMENT
EXECUTE PROCEDURE refresh_events_view();

CREATE TRIGGER refresh_events_view
AFTER INSERT or UPDATE or DELETE or TRUNCATE
ON homework FOR EACH STATEMENT
EXECUTE PROCEDURE refresh_events_view();

这为我们提供了两全其美(至少在我看来),因为mealshomework的每个特定字段仍然存在,我们仍然可以获得始终最新的事件“表“我们可以用来快速查询我们是否只想要了解每个事件的基本信息(即姓名,时间等)。