在父表上存储 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 关系中的特定列的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize:急切加载和排序(在父表上)

MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL

MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL

MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL

MySQL外键设置中的的 CascadeNO ACTIONRestrictSET NULL

更新子表后数据库触发器更新父表上的行