在父表上存储 1:N 关系中的特定列
Posted
技术标签:
【中文标题】在父表上存储 1:N 关系中的特定列【英文标题】:Store specific column from 1:N relationship on parent table 【发布时间】:2021-02-12 07:37:41 【问题描述】:抱歉,问题很长,但我想确保我的问题很清楚。假设我有以下表格:
CREATE TABLE project (
id NUMBER(38, 0),
status_id NUMBER(38, 0), -- FK to a status table
title VARHCAR(4000 CHAR)
);
CREATE TABLE project_status_log (
id NUMBER(38, 0),
project_id NUMBER(38, 0),
status_id NUMBER(38, 0),
user_id NUMBER(38, 0), -- FK to a user table
created_on DATE
);
项目经历一个复杂的工作流程,其中每个状态日志条目代表工作流程中的一个步骤。示例工作流程:草稿 -> 提交 -> 审核 -> 退回草稿 -> 提交 -> 审核 -> 批准
现在假设一个非常常见的需求是获取上次提交项目的用户的user_id
。我通常会创建一个可以加入到project
的视图:
CREATE VIEW project_submitter (project_id, user_id) AS
SELECT project.project_id, submitter.user_id
FROM project
JOIN (
SELECT DISTINCT
project_id,
FIRST_VALUE(user_id) OVER (PARTITION BY project_id ORDER BY date DESC) AS user_id
FROM project_status_log
WHERE status_id = 5 -- ID of submitted status
) submitter
问题是有很多行和很多这样的辅助视图,当我需要在单个查询中使用它们中的许多时,性能会变得非常糟糕。其中一些查询需要几秒钟才能完成。我添加了索引并确保没有全表扫描,但问题似乎在于单个查询中的所有聚合和子查询。
我正在考虑添加一个project.submitted_by
列,该列在项目状态更新为提交时以编程方式设置。这将大大简化我的查询并使生活变得更加轻松。这是一个不好的方法吗?感觉有点像去规范化的数据,但我不确定它是否真的是。
project.submitted_by
列是否存在我没有考虑的潜在问题?如果是这样,除了将整个事物放入弹性搜索索引之外,还有其他方法可以解决性能问题吗?
【问题讨论】:
你为什么要加入内联视图;为什么你在这里使用分析而不是聚合?无论如何......非规范化不一定总是错误的,但最终可能会添加越来越多的列;然后如果状态倒退,您是否清除提交的值,如果是这样,您还需要找到先前的提交者吗?等等。你考虑过物化视图吗? 【参考方案1】:我建议您将状态表与日志表分开。除非您需要获取历史记录,否则不要查询您的日志表。你所做的就是朝着这个方向迈出的一步。
解决问题的其他方法包括
读取模型的创建和维护。 如果您对稍微陈旧的数据感到满意,那么一种捷径是创建定期刷新的物化视图(基于您当前的视图)在您的查询中,您可能想要删除DISTINCT
。你不需要它,因为无论如何你都在做FIRST_VALUE
。
【讨论】:
【参考方案2】:您可以简化查询。一种方法是聚合:
SELECT project_id,
MAX(user_id) KEEP (DENSE_RANK FIRST ORDER BY date DESC) as user_id
FROM project_status_log
WHERE status_id = 5
GROUP BY project_id;
如果您真的想要更多列,或者使用窗口函数:
SELECT . . . -- whatever columns you want
FROM (SELECT psl.*,
ROW_NUMBER() OVER (PARTITION BY project_id ORDER BY date DESC) as seqnum
FROM project_status_log psl
WHERE status_id = 5
) psl
WHERE seqnum = 1;
Oracle 有一个智能优化器,它应该能够为这两个查询使用project_status_log(status_id, project_id, date)
上的索引。
注意:有时视图会阻碍优化器。可能也值得尝试相关子查询:
select p.*,
(select psl.user_id
from project_status_log psl
where psl.status = 5 and psl.project_id = p.project_id
order by date desc
fetch first 1 row only
) as user_id
from projects p;
这里的目标是,任何过滤都意味着子查询仅针对过滤后的行运行。。这也想要上面提到的相同索引。
【讨论】:
以上是关于在父表上存储 1:N 关系中的特定列的主要内容,如果未能解决你的问题,请参考以下文章
MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL
MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL
MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL