优化递归CTE或重写为PL/pgSQL LOOP

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

我在 Postgres 13 版本中有一个带有递归的过程。 它使组织结构从副总裁变成简单的雇主

with recursive relations_recurs(
    pos_id, boss_pos_id, level_num, link_type, link_type_array, pos_id_array
) as (
    select  l.pos_id,
            l.boss_pos_id,
            1 as level_num,
            l.link_type,
            l.link_type_array,
            l.pos_id_array
    from    temp_loop l
    union all
    select  l.pos_id,
            l.boss_pos_id,
            (r.level_num + 1) as level_num,
            l.link_type,
            (r.link_type_array || l.link_type) as link_type_array,
            (r.pos_id_array || l.pos_id) as pos_id_array
    from    temp_pos_boss_with_min_link l
    join    relations_recurs r
    on      l.pos_id = r.boss_pos_id
    and     l.pos_id <> all (r.pos_id_array)
)
select  distinct pos_id_array[1] as pos_id,
        boss_pos_id as boss_pos_id,
        level_num as level_id,
        pos_id as pos_original_id,
        (case 
            when array[1, 2] <@ t.link_type_array then 0
            when array[1] <@ t.link_type_array then 1
            else 2
        end) link_type
from    relations_recurs t;

但是速度很慢。 我正在尝试编写查询不使用递归以提高性能, 有人告诉我使用

WHILE
LOOP can
改进代码。

如何使用

WHILE
LOOP
将其变成程序?

这里是 DDL 的链接

sql postgresql plpgsql recursive-query recursive-cte
1个回答
-1
投票

递归 CTE 对于分层数据结构通常非常有效,尤其是当递归深度不是很深时。但是,如果您认为迭代方法可能更适合您在 PostgreSQL 中的特定用例,您可以尝试使用带循环的 PL/pgSQL 函数。

您必须手动管理递归 CTE 自动执行的迭代和数据积累。这很复杂,尤其是对于分层数据,并且可能不一定会带来性能改进。

这是一个简单的例子:

CREATE OR REPLACE PROCEDURE process_org_structure()
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
    finished BOOLEAN := FALSE;
    current_level INT := 1;
BEGIN
    -- Temporary table to store intermediate results
    CREATE TEMP TABLE IF NOT EXISTS iter_relations (
        pos_id INT,
        boss_pos_id INT,
        level_num INT,
        link_type INT,
        link_type_array INT[],
        pos_id_array INT[]
    );

    -- Initialize the temporary table with the base level data
    INSERT INTO iter_relations(pos_id, boss_pos_id, level_num, link_type, link_type_array, pos_id_array)
    SELECT pos_id, boss_pos_id, 1, link_type, link_type_array, pos_id_array
    FROM temp_loop;

    -- Loop until no more records are added
    LOOP
        -- Check if any new records were added in the last iteration
        GET DIAGNOSTICS rec_count = ROW_COUNT;
        IF rec_count = 0 THEN
            finished := TRUE;
        END IF;

        EXIT WHEN finished;

        -- Iterate to the next level
        INSERT INTO iter_relations(pos_id, boss_pos_id, level_num, link_type, link_type_array, pos_id_array)
        SELECT l.pos_id, l.boss_pos_id, current_level + 1, l.link_type, r.link_type_array || l.link_type, r.pos_id_array || l.pos_id
        FROM temp_pos_boss_with_min_link l
        JOIN iter_relations r ON l.boss_pos_id = r.pos_id
        WHERE l.pos_id <> ALL(r.pos_id_array)
        ON CONFLICT DO NOTHING;

        -- Increment the level for the next iteration
        current_level := current_level + 1;
    END LOOP;

    -- Select the final results from the temporary table
    CREATE TEMP TABLE final_results AS
    SELECT DISTINCT pos_id_array[1] as pos_id,
                    boss_pos_id,
                    level_num,
                    pos_id as pos_original_id,
                    (CASE 
                        WHEN array[1,2] <@ link_type_array THEN 0
                        WHEN array[1] <@ link_type_array THEN 1
                        ELSE 2
                    END) AS link_type
    FROM iter_relations;

    -- Clean up the temporary table
    DROP TABLE iter_relations;

    -- Your logic here to utilize `final_results`
    -- For example, you can return the results or insert them into another table
END;
$$;
© www.soinside.com 2019 - 2024. All rights reserved.