Иерархический отчет о диапазоне контроля в SQL без синтаксиса Oracle CONNECT BY? - PullRequest
0 голосов
/ 21 декабря 2018

Сводка

Диапазон контроля - это количество сотрудников, подотчетных данному руководителю.Прямое и косвенное число отчетов должно быть разделено на их собственные итоги.Необходимы другие подсчеты, в том числе множество вакансий для прямых и косвенных отчетов, имеющихся в организации.Менеджер - это любая должность, которая подчиняется другим позициям.Путь создания отчетов от вершины до любого места в дереве необходим для выравнивания структуры.

Я видел, как эта проблема часто появлялась в проектах кадровой отчетности и хранилища данных.Я только смог решить это в Oracle.Может ли этот отчет быть написан на (ANSI) SQL, который совместим с другой базой данных, такой как SQL Server или PostgreSQL?

Detail

Визуальное представление иерархии организации:

Level 1                                1:3
                                        |
                        ----------------+-----------------------------------
                        |               |               |                  |
Level 2                2:1            13:             10:12               4:2
                        |                               |
               ---------+----------           ----------+----------
               |        |         |           |         |         |
Level 3      12:10     3:        3:         5:10-1    11:11      6:
               |                              |                   |
            ---+---               ------------+------------       |
            |     |               |     |     |     |     |       |
Level 4    7:4   7:9             8:5   8:7   8:6   8:    8:      9:8

Каждый узел или лист дерева представлен одним из следующих элементов:

  • position_id:employee_id
  • position_id:employee_id-multi_job_sequence (если multi_job_sequence>0)
  • position_id: (вакантно)

Ожидаемая производительность

POSITION_ID    POSITION_DESCR         REPORTSTO_POSITION_ID      EMPLOYEE_ID    MULTI_JOB_SEQUENCE      EMPLOYEE_NAME      TREE_LEVEL_NUM      IS_MANAGER     MAX_INCUMBENTS       FILLED_HEAD_COUNT      VACANT_HEAD_COUNT     FILLED_DIRECT_REPORTS     VACANT_DIRECT_REPORTS       FILLED_INDIRECT_REPORTS     VACANT_INDIRECT_REPORTS       EMPLOYEES_UNDER_POSITION        VACANCIES_UNDER_POSITION       REPORTING_PATH_POSITION_ID     REPORTING_PATH_POSITION_DESCR                       REPORTING_PATH_EMPLOYEE        REPORTING_PATH_EMPLOYEE_NAME
1              CEO                    NULL                       3              0                       Jill               1                   1              1                    1                      0                     3                         1                           9                           5                             12                              6                              1                              CEO                                                 3                              Jill
2              Senior Manager         1                          1              0                       Tom                2                   1              1                    1                      0                     1                         2                           2                           0                             3                               2                              1>2                            CEO>Senior Manager                                  3>1                            Jill>Tom
3              West Winger            2                          NULL           NULL                    NULL               3                   0              2                    0                      2                     0                         0                           0                           0                             0                               0                              1>2>3                          CEO>Senior Manager>West Winger                      3>1>(vacant)                   Jill>Tom>(vacant)
4              Executive Assistant    1                          2              0                       Doug               2                   0              1                    1                      0                     0                         0                           0                           0                             0                               0                              1>4                            CEO>Executive Assistant                             3>2                            Jill>Doug
5              Supervisor South       10                         10             1                       Frank              3                   1              1                    1                      0                     3                         2                           0                           0                             3                               2                              1>10>5                         CEO>Senior Manager>Supervisor South                 3>12>10-1                      Jill>Fred>Frank
6              Supervisor East        10                         NULL           NULL                    NULL               3                   1              1                    0                      1                     1                         0                           0                           0                             1                               0                              1>10>6                         CEO>Senior Manager>Supervisor East                  3>12>(vacant)                  Jill>Fred>(vacant)
7              Expert                 12                         4              0                       Olivia             4                   0              2                    2                      0                     0                         0                           0                           0                             0                               0                              1>2>12>7                       CEO>Senior Manager>Supervisor West>Expert           3>1>10>4                       Jill>Tom>Frank>Olivia
7              Expert                 12                         9              0                       David              4                   0              2                    2                      0                     0                         0                           0                           0                             0                               0                              1>2>12>7                       CEO>Senior Manager>Supervisor West>Expert           3>1>10>9                       Jill>Tom>Frank>David
8              Minion                 5                          5              0                       Carol              4                   0              5                    3                      2                     0                         0                           0                           0                             0                               0                              1>10>5>8                       CEO>Senior Manager>Supervisor South>Minion          3>12>10-1>5                    Jill>Fred>Frank>Carol
8              Minion                 5                          6              0                       Mary               4                   0              5                    3                      2                     0                         0                           0                           0                             0                               0                              1>10>5>8                       CEO>Senior Manager>Supervisor South>Minion          3>12>10-1>6                    Jill>Fred>Frank>Mary
8              Minion                 5                          7              0                       Michael            4                   0              5                    3                      2                     0                         0                           0                           0                             0                               0                              1>10>5>8                       CEO>Senior Manager>Supervisor South>Minion          3>12>10-1>7                    Jill>Fred>Frank>Michael
9              Administrator          6                          8              0                       Nigel              4                   0              1                    1                      0                     0                         0                           0                           0                             0                               0                              1>10>6>9                       CEO>Senior Manager>Supervisor East>Administrator    3>12>(vacant)>8                Jill>Fred>(vacant)>Nigel
10             Senior Manager         1                          12             0                       Fred               2                   1              1                    1                      0                     2                         1                           4                           2                             6                               3                              1>10                           CEO>Senior Manager                                  3>12                           Jill>Fred
11             Supervisor South       10                         11             0                       Wilson             3                   0              1                    1                      0                     0                         0                           0                           0                             0                               0                              1>10>11                        CEO>Senior Manager>Supervisor South                 3>12>11                        Jill>Fred>Wilson
12             Supervisor West        2                          10             0                       Frank              3                   1              1                    1                      0                     2                         0                           0                           0                             2                               0                              1>2>12                         CEO>Senior Manager>Supervisor West                  3>1>10                         Jill>Tom>Frank
13             Executive Mid-West     1                          NULL           NULL                    NULL               2                   0              1                    0                      1                     0                         0                           0                           0                             0                               0                              1>13                           CEO>Executive Mid-West                              3>(vacant)                     Jill>(vacant)

Технические требования

  1. * reportsto_position_id содержит position_id менеджера, NULL для верхней позиции.
  2. position_id должен существовать всегда, но может быть свободным.
  3. Менеджеры должныиметь уникальную position_idmax_incumbents=1) для правильной работы дерева.
  4. Аналогичные позиции в разных поддеревьях или на разных уровнях также должны иметь разные position_id для поддержки структуры отчетности.Это связано с тем, что reportsto_position_id определено для каждого узла в дереве.
  5. employee_id может существовать на нескольких узлах, что указывает на то, что сотрудник имеет несколько заданий в организации.Если у сотрудника есть 1 работа, его multi_job_sequence будет 0.Если у сотрудника есть несколько рабочих мест, их multi_job_sequence увеличивается.
  6. Позиции имеют max_incumbents для ограничения количества сотрудников, которым разрешено занимать эту должность.Вакансии не имеют рядов вакансий, но их можно рассчитать.
  7. Должности менеджера могут быть вакантными, даже если сотрудники все еще отчитываются на этой должности.
  8. Если организация решит провести реструктуризацию путем добавления / удаления уровнейили поддеревья, код SQL не должен изменяться.
  9. Этот пример слишком упрощен.Крупные организации могут иметь больше уровней и вариантов для должностей и сотрудников (например, даты вступления в силу или статус).Чтобы уменьшить сложность, все сотрудники и должности в этом примере являются активными.

бизнес-требования отчета о диапазоне контроля

Отчет должен отвечать на следующие вопросы, которыераспространены в иерархических организациях:

  1. Сколько прямых отчетов (количество сотрудников только на один уровень ниже их) есть у менеджера?
  2. Сколько косвенных отчетов (количество сотрудников болееодин уровень ниже их, вплоть до самого нижнего уровня дерева) есть ли у менеджера?
  3. Сколько людей у ​​этого менеджера "под своим положением" (то есть прямых отчетов + косвенных отчетов)?
  4. Сколько менеджеров имеют вакантные должности, которые им необходимо заполнить в своей команде (вакантные прямые отчеты)?
  5. Сколько менеджеров отчитываются перед менеджерами, у которых есть вакансии в своих командах (вакантные косвенные отчеты)?
  6. Каков путь от вершины к каждой позиции в дереве, по имени или идентификатору: например, CEO>Senior Manager>Supervisor South>Minion или 1>2>5>8?
  7. Каков путь от вершины до каждого сотрудника в дереве, по имени или идентификатору (с учетом сотрудников, которые могут иметь несколько заданий): например, Jill>Tom>Frank>Olivia или 3>1>10-1>4?

Пример данных

позиция таблица

position_id  descr                            reportsto_position_id  max_incumbents
1            CEO                              NULL                   1
2            Senior Manager                   1                      1
3            West Winger                      2                      2
4            Executive Assistant              1                      1
5            Supervisor South                 10                     1
6            Supervisor East                  10                     1
7            Expert                           12                     2
8            Minion                           5                      5
9            Administrator                    6                      1
10           Senior Manager                   1                      1
11           Supervisor South                 10                     1
12           Supervisor West                  2                      1
13           Executive Mid-West               1                      1

работа таблица

employee_id  multi_job_sequence  employee_name  position_id
1            0                   Tom            2
2            0                   Doug           4
3            0                   Jill           1
4            0                   Olivia         7
5            0                   Carol          8
6            0                   Mary           8
7            0                   Michael        8
8            0                   Nigel          9
9            0                   David          7
10           0                   Frank          12
10           1                   Frank          5
11           0                   Wilson         11
12           0                   Fred           10

SQL

-- Position incumbents. One row for each position, employee_id, multi_job_sequence combination.
with 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
),
-- Incumbents count (filled and vacant) per position
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
),
-- Count the filled and vacant reports_to positions
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 the organisation tree, based on the reportsto_position_id
cte_reportsto_tree
as
(
    select
    rtt.position_id,
    rtt.employee_id,
    rtt.multi_job_sequence,
    rtt.position_descr,
    rtt.reportsto_position_id,
    rtt.employee_name,
    level as tree_level_num,
    case when connect_by_isleaf = 0 then 1 else 0 end as is_manager,
    rtt.max_incumbents,
    nvl((
        select
        rtc.filled_count
        from cte_reportsto_count rtc
        where rtc.reportsto_position_id = rtt.position_id
    ),0) as filled_direct_reports,
    nvl((
        select
        rtc.vacant_count
        from cte_reportsto_count rtc
        where rtc.reportsto_position_id = rtt.position_id
    ),0) as vacant_direct_reports,
    substr(sys_connect_by_path(rtt.position_id,'>'),2,length(sys_connect_by_path(rtt.position_id,'>'))-1) as reporting_path_position_id,
    substr(sys_connect_by_path(rtt.position_descr,'>'),2,length(sys_connect_by_path(rtt.position_descr,'>'))-1) as reporting_path_position_descr,
    substr(sys_connect_by_path(nvl(case when rtt.employee_id is null then null else case when rtt.multi_job_sequence = 0 then to_char(rtt.employee_id) else rtt.employee_id || '-' || rtt.multi_job_sequence end end,'(vacant)'),'>'),2,length(sys_connect_by_path(nvl(case when rtt.employee_id is null then null else rtt.employee_id || '-' || rtt.multi_job_sequence end,'(vacant)'),'>'))-1) as reporting_path_employee,
    substr(sys_connect_by_path(nvl(rtt.employee_name,'(vacant)'),'>'),2,length(sys_connect_by_path(nvl(rtt.employee_name,'(vacant)'),'>'))-1) as reporting_path_name
    from
    (
        select
        cp.position_id,
        cp.descr as position_descr,
        cp.max_incumbents,
        cp.reportsto_position_id,
        cj.employee_id,
        cj.multi_job_sequence,
        cj.employee_name
        from position cp
        left join job cj on cj.position_id = cp.position_id -- Positions may not be filled
    ) rtt
    connect by prior rtt.position_id = rtt.reportsto_position_id
    start with rtt.reportsto_position_id is null -- Start at the top of the tree
),
-- Create the report detail, traversing the tree (creating subtrees to get the indirect values). This is the tough part!
cte_report_detail
as
(
    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,
    case when soc.is_manager = 1 then
    -- Get the filled count of all of the positions underneath and subtract the direct reports to arrive at the filled indirect reports count
    (
        select
        sum(
             (
                select
                rtc.filled_count
                from cte_reportsto_count rtc
                where rtc.reportsto_position_id = cp.position_id
             )
           )
        from position cp
        connect by prior cp.position_id = cp.reportsto_position_id
        start with cp.position_id = soc.position_id
    ) - soc.filled_direct_reports else 0 end as filled_indirect_reports,
    -- Get the vacant count of all of the positions underneath and subtract the direct reports to arrive at the vacant indirect reports count
    case when soc.is_manager = 1 then
    (
        select
        sum(
             (
                select
                rtc.vacant_count
                from cte_reportsto_count rtc
                where rtc.reportsto_position_id = cp.position_id
             )
           )
        from position cp
        connect by prior cp.position_id = cp.reportsto_position_id
        start with cp.position_id = soc.position_id
    ) - soc.vacant_direct_reports else 0 end as vacant_indirect_reports,
    to_clob(cast(soc.reporting_path_position_id as varchar2(4000))) as reporting_path_position_id,
    to_clob(cast(soc.reporting_path_position_descr as varchar2(4000))) as reporting_path_position_descr,
    to_clob(cast(soc.reporting_path_employee as varchar2(4000))) as reporting_path_employee,
    to_clob(cast(soc.reporting_path_name as varchar2(4000))) as reporting_path_employee_name
    from cte_reportsto_tree soc
)
-- Final calculations and sort
select
r.position_id,
r.position_descr,
r.reportsto_position_id,
r.employee_id,
r.multi_job_sequence,
r.employee_name,
r.tree_level_num,
r.is_manager,
r.max_incumbents,
r.filled_head_count,
r.vacant_head_count,
r.filled_direct_reports,
r.vacant_direct_reports,
r.filled_indirect_reports,
r.vacant_indirect_reports,
(r.filled_direct_reports + r.filled_indirect_reports) as employees_under_position,
(r.vacant_direct_reports + r.vacant_indirect_reports) as vacancies_under_position,
r.reporting_path_position_id,
r.reporting_path_position_descr,
r.reporting_path_employee,
r.reporting_path_employee_name
from cte_report_detail r
order by r.position_id,
         r.employee_id,
         r.multi_job_sequence;

Пример SQL Fiddle

Ответы [ 2 ]

0 голосов
/ 31 января 2019

После небольшой работы мне удалось ответить на мой собственный вопрос, чтобы воспроизвести точно такой же результат с использованием CTE.

Функция рекурсивного CTE работает в Oracle в этой ситуации с некоторыми ограничениями.Другая база данных должна поддерживать рекурсию с поиском DEPTH FIRST вместе с аналитическими функциями.Теоретически этот код может быть перенесен с незначительными изменениями в синтаксис.

Ключевые моменты / извлеченные уроки:

  1. Результаты рекурсивных CTE нельзя использовать в другом CTE или подзапросе,Если вы хотите использовать результаты рекурсивного CTE в подзапросе, вы должны сначала материализовать в таблицу.
  2. Нельзя использовать представление для рекурсивных CTE, иначе вы получите ошибку ORA-01702: a view is not appropriate here.
  3. Предложение SEARCH DEPTH FIRST BY reportsto_position_id SET seq было важно для установки is_manager с помощью аналитической функции LEAD().
  4. Уплощение дерева в поля reporting_path было полезно для его правильного обхода.,Я использовал функцию INSTR(), чтобы гарантировать наличие позиции в заданном пути.
  5. У скрипта SQL ограничение в 8000 символов, что вынудило меня материализовать другие CTE, прежде чем я смог запустить отчет.В обычной базе данных Oracle в этом нет необходимости, поскольку размер запроса не ограничен.

Образцы таблиц данных и базовые значения

-- 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);

Отчет с использованием CTE

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

0 голосов
/ 21 декабря 2018

Короче говоря, ответ - да.

Стандартный SQL: 1999 определяет "рекурсивные выражения CTE" (выражения рекурсивных общих таблиц), которые выполняют работу CONNECT BY и многое другое.Они предназначены для работы с любыми типами графов - иерархии являются подмножеством того, что они могут обрабатывать.

Ваш запрос довольно обширен, поэтому у меня нет времени, чтобы просмотреть его и переписать в стандартном SQL.

Вы спрашиваете, какие базы данных могут это сделать.Ну, в настоящее время они реализуются:

  • Oracle.
  • DB2.Не реализует обнаружение цикла в Linux / Unix / Windows.Это в z / OS.
  • PostgreSQL.
  • SQL Server (с 2012 года?).Не реализует обнаружение цикла.
  • MariaDB, начиная с 10.2.Не реализует обнаружение цикла.
  • MySQL с 8.0.Не реализует обнаружение цикла.
  • H2 (начиная с 1.4?).Не реализует обнаружение цикла.
  • HyperSQL.
  • другие базы данных ...

Если вы приведете меньший пример, мне было бы весьма интересно перефразировать его, используярекурсивный CTE.

Например, следующий рекурсивный CTE (в Oracle) найдет все поддерево сотрудников, которые сообщают (прямо и косвенно) о положении = 2:

with
x (position_id, descr, reportsto_position_id, max_incumbents, cur_level) as (
  select
    position_id, descr, reportsto_position_id, max_incumbents,
    1
    from position
    where position_id = 2 -- start at position = 2
  union all
  select
    p.position_id, p.descr, p.reportsto_position_id, p.max_incumbents,
    x.cur_level + 1
    from position p
    join x on p.reportsto_position_id = x.position_id
)
select * from x;
...