Как динамически выполнять вычисления с помощью оператора CASE на основе результатов вычислений в предыдущей строке в Oracle? - PullRequest
0 голосов
/ 13 декабря 2018

Я пытаюсь выполнить вычисления с помощью CASE операторов, которые основаны на результатах вычислений, выполненных в предыдущей строке.Данные, с которыми я работаю, являются иерархическими данными.Моя конечная цель состоит в том, чтобы структурировать полученные данные так, чтобы они соответствовали Модифицированному алгоритму обхода дерева предзаказов .

Вот как выглядят мои необработанные данные:

+-------+--------+
| id    | parent |
+-------+--------+
| 1     | (null) |
+-------+--------+
| 600   | 1      |
+-------+--------+
| 690   | 600    |
+-------+--------+
| 6990  | 690    |
+-------+--------+
| 6900  | 690    |
+-------+--------+
| 69300 | 6900   |
+-------+--------+
| 69400 | 6900   |
+-------+--------+

Вот то, что я хочу, чтобы конечный результат был похож.Я с удовольствием расскажу, почему это то, что я ищу, связано с MPTT и т. Д.

+-------+-----------+-----+------+--+--+--+--+
| id    | parent_id | lft | rght |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 1     |           | 1   | 14   |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 600   | 1         | 2   | 13   |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 690   | 600       | 3   | 12   |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 6900  | 690       | 4   | 9    |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 6990  | 690       | 10  | 11   |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 69300 | 6900      | 5   | 6    |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+
| 69400 | 6900      | 7   | 8    |  |  |  |  |
+-------+-----------+-----+------+--+--+--+--+

Вот как выглядит мой SQL-код.Он вычисляет многие поля, которые, как мне кажется, требует алгоритм, который я опишу ниже.Это «организационные» данные в корпоративной среде, поэтому аббревиатура orgn является обычной в моем коде.

Вот алгоритм, который, я думаю, успешно преобразует их в формат MPTT:

-If level is root (lvl=1), lft = 1, rght = subnodes*2 + 2
-If level is the next level down (lvl = prev_lvl+1), and prev_parent != parent (meaning this is the first sibling)
    -lft = parent_lft+1
-If lvl = prev_lvl, so we are on the same level (don’t know if this is a true sibling of the same parent yet)
    -if parent = prev_parent, lft=prev_rght+1 (true sibling, just use previous sibling’s right + 1)
    -if parent != prev_parent, lft=parent_lft+1 (same level, not true sibling, so use parent’s left + 1)

-rght=(subnodes*2) + lft + 1

SQL-код, который у меня есть на данный момент:

WITH tab1 (
    id,
    parent_id
) AS (
    SELECT
        1,
        NULL
    FROM
        dual
    UNION ALL
    SELECT
        600,
        1
    FROM
        dual
    UNION ALL
    SELECT
        690,
        600
    FROM
        dual
    UNION ALL
    SELECT
        6990,
        690
    FROM
        dual
    UNION ALL
    SELECT
        6900,
        690
    FROM
        dual
    UNION ALL
    SELECT
        69300,
        6900
    FROM
        dual
    UNION ALL
    SELECT
        69400,
        6900
    FROM
        dual
),t1 (
    id,
    parent_id,
    lvl
) AS (
    SELECT
        id,
        parent_id,
        1 AS lvl
    FROM
        tab1
    WHERE
        parent_id IS NULL
    UNION ALL
    SELECT
        t2.id,
        t2.parent_id,
        lvl + 1
    FROM
        tab1 t2,
        t1
    WHERE
        t2.parent_id = t1.id
)
    SEARCH BREADTH FIRST BY id SET order1,orgn_subnodes AS (
    SELECT
        id AS id,
        COUNT(*) - 1 AS subnodes
    FROM
        (
            SELECT
                CONNECT_BY_ROOT ( t1.id ) AS id
            FROM
                t1
            CONNECT BY
                PRIOR t1.id = t1.parent_id
        )
    GROUP BY
        id
),orgn_partial_data AS (
    SELECT
        orgn_subnodes.id AS id,
        orgn_subnodes.subnodes,
        parent_id,
        lvl,
        LAG(lvl,1) OVER(
            ORDER BY
                order1
        ) AS prev_lvl,
        LAG(parent_id,1) OVER(
            ORDER BY
                order1
        ) AS prev_parent,
        CASE
                WHEN parent_id IS NULL THEN 1
            END
        lft,
        CASE
                WHEN parent_id IS NULL THEN ( subnodes * 2 ) + 2
            END
        rght,
        order1
    FROM
        orgn_subnodes
        JOIN t1 ON orgn_subnodes.id = t1.id
) SELECT
    *
  FROM
    orgn_partial_data;

Результат:

+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| id    | subnodes | parent_id | lvl | prev_lvl | prev_parent | lft | rght | order1 |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 1     | 6        |           | 1   |          |             | 1   | 14   | 1      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 600   | 5        | 1         | 2   | 1        |             |     |      | 2      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 690   | 4        | 600       | 3   | 2        | 1           |     |      | 3      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 6900  | 2        | 690       | 4   | 3        | 600         |     |      | 4      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 6990  | 0        | 690       | 4   | 4        | 690         |     |      | 5      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 69300 | 0        | 6900      | 5   | 4        | 690         |     |      | 6      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+
| 69400 | 0        | 6900      | 5   | 5        | 6900        |     |      | 7      |
+-------+----------+-----------+-----+----------+-------------+-----+------+--------+

Меня не волнует порядок «узловых узлов» в дереве,Кроме того, если вы не нашли полезный SQL-запрос, вы можете опубликовать ответ, в котором он не используется.Я только написал, чтобы показать, какую информацию я думаю, что мне нужно выполнить шаги алгоритма.

Я приму любой код Oracle (процедура базы данных, оператор SELECT и т. Д.) В качестве ответа.

Пожалуйста, запросите более подробную информацию, если они вам нужны!

1 Ответ

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

Я думаю, что в начальном посте есть опечатка, она должна быть (7, 8), а не (4, 8) для 69400.

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

Временная таблица

create global temporary table tmp$ (id int, l int, r int) on commit delete rows;

Пакет

create or replace package pkg as
  procedure p(p_id in int);
end pkg;
/
sho err

Тело пакета

create or replace package body pkg as

  seq int;

  procedure p_(p_id in int) as
  begin
    seq := seq + 1;
    insert into tmp$(id, l, r) values (p_id, seq, null);
    for i in (select id from tab1 where parent_id = p_id order by id) loop
      p_(i.id);
    end loop;
    seq := seq + 1;    
    update tmp$ set r = seq where id = p_id;
  end;

  procedure p(p_id in int) as
  begin
    seq := 0;
    p_(p_id);
  end;  

end pkg;
/
sho err

Тест в SQL * PLus

SQL> exec pkg.p(1);

PL/SQL procedure successfully completed.

SQL> select * from tmp$;

        ID          L          R
---------- ---------- ----------
         1          1         14
       600          2         13
       690          3         12
      6900          4          9
     69300          5          6
     69400          7          8
      6990         10         11

7 rows selected.

Обновление

Автономная процедура без глобальных переменных

create or replace procedure p(p_id in int, seq in out int) as
begin
  seq := seq + 1;
  insert into tmp$(id, l, r) values (p_id, seq, null);
  for i in (select id from tab1 where parent_id = p_id order by id) loop
    p(i.id, seq);
  end loop;
  seq := seq + 1;
  update tmp$ set r = seq where id = p_id;
end;
/

Тест в SQL* PLus

SQL> var n number
SQL> exec :n := 0;

PL/SQL procedure successfully completed.

SQL> exec p(1, :n);

PL/SQL procedure successfully completed.

SQL> select * from tmp$;

        ID          L          R
---------- ---------- ----------
         1          1         14
       600          2         13
       690          3         12
      6900          4          9
     69300          5          6
     69400          7          8
      6990         10         11

7 rows selected.
...