我是使用数据库的新手,我正在尝试设计一个新的数据库,我认为我需要在许多表中建立一对一的关系。
为了演示我的设计,我们假设我正在建立一个计划数据库作为示例。我首先为一个具有一对多关系的人创建一个表
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
以及person
和events
之间使用两个events
语句,但如果我只是想通过Brads事件和获取有关每个事件的所有额外信息(例如,如果事件是用餐,告诉我他吃了什么,如果是功课,告诉我他得到的分数)?
总的来说,我有几个问题。
meals
列替换type
列)但我认为我在某个地方读到了一个坏主意。其他一些说明,我正在使用Postgresql作为数据库。我所建立的实际数据库除了我在此处显示的内容外,还为每个表提供了更详细的信息。我只想弄清楚我想要达到的目的。最后,我正在使用sqlalchemy的ORM构建/访问数据库,所以如果有一个很好的技巧我可以使用table
来帮助解决这个问题,这对我们来说也是非常有用的。
答案 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
表,而是创建一个物化视图来表示事件信息。首先,我们需要修改meals
和homework
表的设置方式,并包含以前在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
视图始终是最新的,我们可以创建触发器,以便在meals
或homework
根据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();
这为我们提供了两全其美(至少在我看来),因为meals
和homework
的每个特定字段仍然存在,我们仍然可以获得始终最新的事件“表“我们可以用来快速查询我们是否只想要了解每个事件的基本信息(即姓名,时间等)。