您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

没有Oracle CONNECT BY语法的SQL中的分层控制范围报告?

没有Oracle CONNECT BY语法的SQL中的分层控制范围报告?

经过一些工作,我设法回答了自己的问题,以使用CTE再现完全相同的结果。

在这种情况下,递归CTE功能可在Oracle中使用,但有一些限制。另一个数据库将需要支持DEPTH FIRST搜索递归以及分析功能。从理论上讲,此代码可以通过对语法进行小的更改来移植。

要点/经验教训:

-- Create a table for each current position.
create table position
(
    position_id           NUMBER(11) NOT NULL,
    descr                 VARCHAR2(50) NOT NULL,
    reportsto_position_id NUMBER(11),
    max_incumbents        NUMBER(4) NOT NULL
);

create unique index position_idx1 on position (position_id);

-- Create a table to store the current job data.
create table job
(
    employee_id        NUMBER(11) NOT NULL,
    multi_job_sequence NUMBER(1) NOT NULL,
    employee_name      VARCHAR2(50) NOT NULL,
    position_id        NUMBER(11) NOT NULL
);

create unique index job_idx1 on job (employee_id, multi_job_sequence);
create index job_idx2 on job (position_id, employee_id, multi_job_sequence);

-- Insert data into position table
insert into position values (1, 'CEO', NULL, 1);
insert into position values (2, 'Senior Manager', 1, 1);
insert into position values (3, 'West Winger', 2, 2);
insert into position values (4, 'Executive Assistant', 1, 1);
insert into position values (5, 'Supervisor South', 10, 1);
insert into position values (6, 'Supervisor East', 10, 1);
insert into position values (7, 'Expert', 12, 2);
insert into position values (8, 'Minion', 5, 5);
insert into position values (9, 'Administrator', 6, 1);
insert into position values (10, 'Senior Manager', 1, 1);
insert into position values (11, 'Supervisor South', 10, 1);
insert into position values (12, 'Supervisor West', 2, 1);
insert into position values (13, 'Executive Mid-West', 1, 1);

commit;

-- Insert data into job table
insert into job values (1, 0, 'Tom', 2);
insert into job values (2, 0, 'Doug', 4);
insert into job values (3, 0, 'Jill', 1);
insert into job values (4, 0, 'Olivia', 7);
insert into job values (5, 0, 'Carol', 8);
insert into job values (6, 0, 'Mary', 8);
insert into job values (7, 0, 'Michael', 8);
insert into job values (8, 0, 'Nigel', 9);
insert into job values (9, 0, 'David', 7);
insert into job values (10, 0, 'Frank', 12);
insert into job values (10, 1, 'Frank', 5);
insert into job values (11, 0, 'Wilson', 11);
insert into job values (12, 0, 'Fred', 10);

commit;

-- Build up the tables

-- Position incumbents. One row for each position, employee_id, multi_job_sequence combination.
create table cte_incumbents
as
(
    select
    cp.position_id,
    cp.reportsto_position_id,
    cp.max_incumbents,
    cj.employee_id,
    cj.multi_job_sequence
    from position cp
    left join job cj on cj.position_id = cp.position_id
);

create unique index cte_incumbents_idx1 on cte_incumbents (position_id, employee_id, multi_job_sequence);
create index cte_incumbents_idx2 on cte_incumbents (position_id, reportsto_position_id);

-- Incumbents count (filled and vacant) per position
create table cte_incumbents_count
as
(
    select
    i.reportsto_position_id,
    i.position_id,
    count(to_char(i.employee_id) || '-' || to_char(i.multi_job_sequence)) as filled_count,
    (i.max_incumbents - count(to_char(i.employee_id) || '-' || to_char(i.multi_job_sequence))) as vacant_count,
    i.max_incumbents
    from cte_incumbents i
    where i.employee_id is not null
    group by i.reportsto_position_id,
             i.position_id,
             i.max_incumbents

    UNION ALL

    select
    i.reportsto_position_id,
    i.position_id,
    0 as filled_count,
    (count(*) * i.max_incumbents) as vacant_count,
    i.max_incumbents
    from cte_incumbents i
    where i.employee_id is null
    group by i.reportsto_position_id,
             i.position_id,
             i.max_incumbents
);

create unique index cte_incumbents_count_idx on cte_incumbents_count (reportsto_position_id, position_id);


-- Count the filled and vacant reports_to positions
create table cte_reportsto_count
as
(
    select
    i.reportsto_position_id,
    sum(i.filled_count) as filled_count,
    sum(i.vacant_count) as vacant_count,
    sum(i.max_incumbents) as total_incumbents
    from cte_incumbents_count i
    group by i.reportsto_position_id
);

create unique index cte_reportsto_count_idx on cte_reportsto_count (reportsto_position_id);

create table cte_reportsto_tree as
-- Create the organisation tree, based on the reportsto_position_id
with cte_reportsto_tree_base (
                                 position_id,
                                 position_descr,
                                 reportsto_position_id,
                                 employee_id,
                                 multi_job_sequence,
                                 employee_name,
                                 tree_level_num,
                                 max_incumbents,
                                 filled_direct_reports,
                                 vacant_direct_reports,
                                 reporting_path_position_id,
                                 reporting_path_position_descr,
                                 reporting_path_employee,
                                 reporting_path_employee_name
                             )
as
(
    -- Anchor member
    select
    cp1.position_id,
    cp1.descr as position_descr,
    cp1.reportsto_position_id,
    cj1.employee_id,
    cj1.multi_job_sequence,
    cj1.employee_name,
    1 as tree_level_num,
    cp1.max_incumbents,
    nvl((
        select
        rtc.filled_count
        from cte_reportsto_count rtc
        where rtc.reportsto_position_id = cp1.position_id
    ),0) as filled_direct_reports,
    nvl((
        select
        rtc.vacant_count
        from cte_reportsto_count rtc
        where rtc.reportsto_position_id = cp1.position_id
    ),0) as vacant_direct_reports,
    to_char(cp1.position_id) as reporting_path_position_id,
    cp1.descr as reporting_path_position_descr,
    to_char(cj1.employee_id) as reporting_path_employee,
    cj1.employee_name as reporting_path_employee_name
    from position cp1
    left join job cj1 on cj1.position_id = cp1.position_id -- Positions may not be filled
    where cp1.position_id = 1 -- start at position = 1

    UNION ALL

    -- Recursive member
    select
    cp2.position_id,
    cp2.descr as position_descr,
    cp2.reportsto_position_id,
    cj2.employee_id,
    cj2.multi_job_sequence,
    cj2.employee_name,
    rtt.tree_level_num + 1 as tree_level_num,
    cp2.max_incumbents,
    nvl((
        select
        rtc.filled_count
        from cte_reportsto_count rtc
        where rtc.reportsto_position_id = cp2.position_id
    ),0) as filled_direct_reports,
    nvl((
        select
        rtc.vacant_count
        from cte_reportsto_count rtc
        where rtc.reportsto_position_id = cp2.position_id
    ),0) as vacant_direct_reports,
    rtt.reporting_path_position_id || '>' || to_char(cp2.position_id) as reporting_path_position_id,
    rtt.reporting_path_position_descr || '>' || cp2.descr as reporting_path_position_descr,
    rtt.reporting_path_employee || '>' || nvl(case when cj2.employee_id is null then null else case when cj2.multi_job_sequence = 0 then to_char(cj2.employee_id) else to_char(cj2.employee_id) || '-' || to_char(cj2.multi_job_sequence) end end,'(vacant)') as reporting_path_employee,
    rtt.reporting_path_employee_name || '>' || nvl(cj2.employee_name,'(vacant)') as reporting_path_employee_name
    from position cp2
    inner join cte_reportsto_tree_base rtt on rtt.position_id = cp2.reportsto_position_id
    left join job cj2 on cj2.position_id = cp2.position_id -- Positions may not be filled
)
SEARCH DEPTH FIRST BY reportsto_position_id SET seq
select
rtt.position_id,
rtt.position_descr,
rtt.reportsto_position_id,
rtt.employee_id,
rtt.multi_job_sequence,
rtt.employee_name,
rtt.tree_level_num,
rtt.max_incumbents,
rtt.filled_direct_reports,
rtt.vacant_direct_reports,
rtt.reporting_path_position_id,
rtt.reporting_path_position_descr,
rtt.reporting_path_employee,
rtt.reporting_path_employee_name,
case when (rtt.tree_level_num - lead(rtt.tree_level_num) over (order by seq)) < 0 then 1 else 0 end is_manager -- Is a manager if there is a difference between levels on the tree.
from cte_reportsto_tree_base rtt;

create index cte_reportsto_tree_idx on cte_reportsto_tree (position_id, reportsto_position_id, employee_id, multi_job_sequence);

create table cte_fir as
(
    select
    soc.position_id,
    soc.is_manager,
    soc.tree_level_num,
    soc.filled_direct_reports,
    soc.vacant_direct_reports,
    case when soc.is_manager = 1 then
        nvl((
            select
            sum(ind.filled_direct_reports)
            from cte_reportsto_tree ind
            where ind.tree_level_num > soc.tree_level_num -- Must be at a lower level
        ),0)
    else 0 end as filled_indirect_reports,
    case when soc.is_manager = 1 then
        nvl((
            select
            sum(ind.vacant_direct_reports)
            from cte_reportsto_tree ind
            where ind.tree_level_num > soc.tree_level_num -- Must be at a lower level
        ),0)
    else 0 end as vacant_indirect_reports
    from cte_reportsto_tree soc
    where soc.tree_level_num = 1

    UNION ALL

    select
    soc.position_id,
    soc.is_manager,
    soc.tree_level_num,
    soc.filled_direct_reports,
    soc.vacant_direct_reports,
    case when soc.is_manager = 1 then
        nvl((
            select
            sum(ind.filled_direct_reports)
            from cte_reportsto_tree ind
            where ind.tree_level_num > soc.tree_level_num -- Must be at a lower level
            and instr(ind.reporting_path_position_id, '>'|| soc.position_id || '>') > 0 -- The position must be in the flattened tree path (quick way of traversing the tree)
        ),0)
    else 0 end as filled_indirect_reports,
    case when soc.is_manager = 1 then
        nvl((
            select
            sum(ind.vacant_direct_reports)
            from cte_reportsto_tree ind
            where ind.tree_level_num > soc.tree_level_num -- Must be at a lower level
            and instr(ind.reporting_path_position_id, '>'|| soc.position_id ||'>') > 0 -- The position must be in the flattened tree path (quick way of traversing the tree)
        ),0)
    else 0 end as vacant_indirect_reports
    from cte_reportsto_tree soc
    where soc.tree_level_num > 1
);

create index cte_fir_idx on cte_fir (position_id);

select
soc.position_id,
soc.position_descr,
soc.reportsto_position_id,
soc.employee_id,
soc.multi_job_sequence,
soc.employee_name,
soc.tree_level_num,
soc.is_manager,
soc.max_incumbents,
nvl(
    (
        select
        ic.filled_count
        from cte_incumbents_count ic
        where ic.position_id = soc.position_id
    ),0) as filled_head_count,
nvl(
    (
        select
        ic.vacant_count
        from cte_incumbents_count ic
        where ic.position_id = soc.position_id
    ),0) as vacant_head_count,
soc.filled_direct_reports as filled_direct_reports,
soc.vacant_direct_reports as vacant_direct_reports,
(
    select
    sum(fir.filled_indirect_reports)
    from cte_fir fir
    where fir.position_id = soc.position_id
) as filled_indirect_reports,
(
    select
    sum(fir.vacant_indirect_reports)
    from cte_fir fir
    where fir.position_id = soc.position_id
) as vacant_indirect_reports,
(
    select
    sum(fir.filled_indirect_reports)
    from cte_fir fir
    where fir.position_id = soc.position_id
) + soc.filled_direct_reports as employees_under_position,
(
    select
    sum(fir.vacant_indirect_reports)
    from cte_fir fir
    where fir.position_id = soc.position_id
) + soc.vacant_direct_reports as vacancies_under_position,
soc.reporting_path_position_id,
soc.reporting_path_position_descr,
soc.reporting_path_employee,
soc.reporting_path_employee_name
from cte_reportsto_tree soc
order by soc.position_id,
         soc.employee_id,
         soc.multi_job_sequence;

SQL Fiddle示例

SQLServer 2022/1/1 18:46:41 有435人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶