这是查询项目资源管理数据的好方法吗?

问题描述 投票:0回答:0

如何构造数据库查询以有效获取项目的资源管理数据?

我有一个 Projects 表,其中包含 id、shortId、name、startDate、dueDate、categoryId 和 effort 字段。 categoryId指的是Categories表中的一个类别,包含n个类别和子类别的邻接表模型层级。根类别节点称为团队。 Projects 表还有一个 ProjectMembers 表,该表使用 projectId 和 userId 字段跟踪项目成员。

我想要完成的是计算每个团队和用户在一段时间间隔(可能是几个月或几周)内的相应工作量。请务必注意,用户可能跨类别工作,因此某些团队的资源可能包含来自其他团队的用户。

目前,我创建了一个查询,该查询使用公用表表达式 (CTE) 来计算每个项目在一个月间隔内的比例工作量,使用项目运行的天数除以每个间隔内的天数。

WITH RECURSIVE cte AS (
    SELECT
        "id",
        "name",
        "shortId",
        "categoryId",
        DATE_TRUNC('day', "startDate") AS "startDate",
        DATE_TRUNC('day', "dueDate") AS "dueDate",
        "effort",
        DATE_TRUNC('month', "startDate") AS "month_start",
        DATE_TRUNC('month', "startDate") + INTERVAL '1 MONTH' - INTERVAL '1 day' AS "month_end"
FROM
    "Projects"
WHERE
    "startDate" IS NOT NULL AND
    "dueDate" IS NOT NULL AND
    "dueDate" >= "startDate" AND
    "effort" IS NOT NULL
UNION ALL
SELECT
    "id",
    "name",
    "shortId",
    "categoryId",
    "startDate",
    "dueDate",
    "effort",
    DATE_TRUNC('month', "month_start" + INTERVAL '1 MONTH'),
    DATE_TRUNC('month', DATE_TRUNC('month', "month_start" + INTERVAL '1 MONTH')) + INTERVAL '1 MONTH' - INTERVAL '1 day' AS "month_end"
FROM
    cte
WHERE
    "month_start" + INTERVAL '1 MONTH' <= "dueDate"
    )
SELECT
    "id",
    "name",
    "shortId",
    "categoryId",
    "startDate",
    "dueDate",
    "month_start",
    "month_end",
    EXTRACT(MONTH FROM "month_start") AS "month",
    EXTRACT(YEAR FROM "month_start") AS "year",
    "effort",
    effort * (
--         Get the amount of days falling within interval
            CASE
--             Both start and due date fall within the specified interval
                WHEN "startDate" >= "month_start" AND "dueDate" <= "month_end" THEN
                    EXTRACT(DAY FROM (date_trunc('day', "dueDate") - date_trunc('day', "startDate") + INTERVAL '1 day'))
--             Neither start nor due date fall within the specified interval, thus the entire interval is taken into account
                WHEN "startDate" <= "month_start" AND "dueDate" >= "month_end" THEN
                    EXTRACT(DAY FROM (date_trunc('day', "month_end") - date_trunc('day', "month_start") + INTERVAL '1 day'))
                ELSE
--                 One of the start or due dates fall within the interval, it will be the lesser one
                    LEAST (
                            EXTRACT(DAY FROM (date_trunc('day', "dueDate") - date_trunc('day', "month_start") + INTERVAL '1 day')),
                            EXTRACT(DAY FROM (date_trunc('day', "month_end") - date_trunc('day', "startDate") + INTERVAL '1 day'))
                        )
                END / EXTRACT(DAY FROM (date_trunc('day', "month_end") - date_trunc('day', "month_start") + INTERVAL '1 day')) -- Divided by the span of the interval
        )::numeric(10, 4) AS proportional_effort
FROM cte
ORDER BY id, month_start

然后,我有另外三个 CTE:Projects、Teams 和 ProjectMembers。

Projects 按项目 ID 对 ProjectData 行进行分组,以获取每个项目的 ID、名称、开始日期、截止日期和工作量。

SELECT
    id,
    name,
    "startDate",
    "dueDate",
    effort
FROM "ProjectData"
GROUP BY id, name, "startDate", "dueDate", effort
ORDER BY name

Teams 包含一个子查询,该子查询从 ProjectData 中获取数据并按类别 ID 和间隔对其进行分组,对每个按比例的工作量求和,并在 json 聚合中捕获每个组的相关项目 ID。然后它创建一个 json 对象的 json 聚合(对于每个间隔,在这种情况下为几个月)并在主查询中按根类别(团队)对其进行分组。

WITH RECURSIVE "Teams" ("id", "name", "categoryId", "childId") AS (
    SELECT id, name, "categoryId", id
    FROM "Categories"
    WHERE "categoryId" IS NULL
    UNION ALL
    SELECT t."id", t."name", c."categoryId", c."id"
    FROM "Teams" t
             JOIN "Categories" c ON c."categoryId" = t."childId"
)
SELECT
t."id" AS "categoryId",
t."name" AS "name",
COALESCE(
        json_agg(
            json_build_object(
                    'month', TO_CHAR(month_start, 'DD-MM-YYYY'),
                    'proportionalEffort', proportional_effort
                    ,'projectMap', project_map
            )
        ) FILTER (WHERE month_start IS NOT NULL OR proportional_effort IS NOT NULL OR project_map IS NOT NULL), '[]'::json
    ) AS data
FROM "Teams" t
LEFT JOIN (
    SELECT
    t1.id AS "teamId",
     "month_start",
     SUM(proportional_effort) AS proportional_effort,
     json_agg(
         json_build_object(
             'id', pd.id,
             'effort', pd.proportional_effort
             )
        ) AS project_map
    FROM "ProjectData" pd
    LEFT JOIN "Teams" t1 ON t1."childId" = pd."categoryId"
    GROUP BY t1.id, month_start
    ORDER BY month_start
) cd ON cd."teamId" = t.id
WHERE t."categoryId" IS NULL
GROUP BY t.id, t.name

ProjectMembers 采用与 Teams 类似的方法,它包含一个子查询,该子查询按项目成员和间隔对 ProjectData 行进行分组,总结每个组的比例工作量,并为每个组附加一个 json projectIds 聚合。

SELECT
    id,
    json_agg(
        json_build_object(
            'month', TO_CHAR(month_start, 'DD-MM-YYYY'),
            'effort', proportional_effort,
            'projectMap', project_map
            )
        ) AS months
FROM (
    SELECT
        pm."userId" AS id,
        month_start,
        SUM(proportional_effort) AS proportional_effort,
        jsonb_agg(pd.id) AS project_map
    FROM "ProjectData" pd
    LEFT JOIN "ProjectMembers" pm ON pm."projectId" = pd.id AND pm.type = 'Owner'
    GROUP BY pm."userId", month_start
    ORDER BY pm."userId", month_start
) md
GROUP BY id

我计划将主查询中 Projects、Teams 和 ProjectMembers CTE 中的所有数据合并为一行,每行三个 json 聚合列,有效地为我提供了三个 json 对象数组,我可以在前端使用它们来构建一个可以向下钻取以显示项目成员的团队的表格/甘特图。我将通过在 3 个 CTE 上执行 FULL OUTER JOIN 来执行此操作,可能会为它们提供行号并加入行号。然后我将再次使用 json_agg(json_build_object()) 来构建对象的 json 数组。

拥有 ProjectData CTE 背后的想法是创建一个项目列表,一次按时间间隔按比例分配工作量,然后使用它为团队和项目成员生成必要的数据,因为项目成员可以跨多个团队工作。

我希望通过构建一个项目列表,以及团队和项目成员以及努力数据和 projectId 地图,将使我能够灵活地从不同的角度查看数据,无论是从团队、用户还是项目方面。

我还想在他们的主要查询中实际包含 Teams 和 ProjectMembers 的 projectId 映射,而不是嵌套在 month json 对象中。我不确定如何重写查询来实现这一点,因为我按成员/团队和时间间隔分组以总结相应的努力。

但是,我不确定这是否是最好的方法,或者是否有更有效的方法从数据库中查询数据。如果我要更改此方法以查看周间隔而不是月间隔,性能是否会因为 ProjectData CTE 返回的记录量而成为一个很大的问题?

我正在寻找建议、见解和这种方法可能存在的问题。任何建议将不胜感激。

database postgresql subquery relational-database common-table-expression
© www.soinside.com 2019 - 2024. All rights reserved.