powered by simpleCommunicator - 2.0.53     © 2025 Programmizd 02
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Рассчет дерева Nested Sets
21 сообщений из 21, страница 1 из 1
Рассчет дерева Nested Sets
    #39369046
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Добрый вечер!

Пытаюсь рассчитать дерево для следующей таблицы:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
CREATE TABLE FIAS$ADDRESSES (
    OBJECT_ID    D$GUID NOT NULL /* D$GUID = CHAR(16) */,
    PARENT_FK    D$GUID /* D$GUID = CHAR(16) */,
    "LEVEL"      D$SMALLINT NOT NULL /* D$SMALLINT = SMALLINT */,
    NS_ID        D$INTEGER DEFAULT 0x7FFFFFFF NOT NULL /* D$INTEGER = INTEGER */,
    NS_LEFT      D$INTEGER DEFAULT 0 NOT NULL /* D$INTEGER = INTEGER */,
    NS_RIGHT     D$INTEGER DEFAULT 0 NOT NULL /* D$INTEGER = INTEGER */
);
ALTER TABLE FIAS$ADDRESSES ADD CONSTRAINT FIAS$ADDRESSES$PK PRIMARY KEY (OBJECT_ID);
ALTER TABLE FIAS$ADDRESSES ADD CONSTRAINT FIAS$ADDRESSES_FIAS$ADDRESSES FOREIGN KEY (PARENT_FK) REFERENCES FIAS$ADDRESSES (OBJECT_ID);
CREATE INDEX FIAS$ADDRESSES$IDX_PFK_LVL    ON FIAS$ADDRESSES (PARENT_FK, "LEVEL");
CREATE INDEX FIAS$ADDRESSES$TMP_NS_ID      ON FIAS$ADDRESSES (NS_ID);

CREATE OR ALTER PROCEDURE FIAS$BUILD$08
AS
DECLARE VARIABLE OBJECT_ID D$GUID;
DECLARE VARIABLE COUNTER D$INTEGER;
DECLARE VARIABLE MAX_COUNTER D$INTEGER;
DECLARE VARIABLE CURRENT_ID D$INTEGER;
BEGIN
  INSERT INTO FIAS$ADDRESSES (OBJECT_ID, "LEVEL", REGION_CODE, NAME, FORMAL_NAME, PREFIX_NAME, KLADR_ID, NS_ID, NS_LEFT, NS_RIGHT)
    VALUES (CHAR_TO_UUID('FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF'), 1, 0, 'ROOT', 'ROOT', 'АО', '', 0, 1, 0);
  UPDATE FIAS$ADDRESSES SET
    PARENT_FK = CHAR_TO_UUID('FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF')
  WHERE PARENT_FK IS NULL
    AND OBJECT_ID <> CHAR_TO_UUID('FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF');

  CURRENT_ID  = 0;
  COUNTER     = 0;
  MAX_COUNTER = (SELECT 2 * COUNT(1) FROM FIAS$ADDRESSES);

  WHILE (COUNTER < (MAX_COUNTER - 1)) DO
  BEGIN
    COUNTER   = COUNTER + 1;
    OBJECT_ID = NULL;
    SELECT FIRST 1 P.OBJECT_ID
    FROM FIAS$ADDRESSES A
    INNER JOIN FIAS$ADDRESSES P ON P.PARENT_FK = A.OBJECT_ID
      AND P.NS_LEFT = 0
    WHERE A.NS_ID = :CURRENT_ID
    INTO :OBJECT_ID;

    IF (OBJECT_ID IS NOT NULL) THEN
    BEGIN -- PUSH WHEN TOP HAS SUBORDINATES, SET LEFT_KEY VALUE
      UPDATE FIAS$ADDRESSES SET
        NS_ID   = :CURRENT_ID + 1,
        NS_LEFT = :COUNTER
      WHERE OBJECT_ID = :OBJECT_ID;
      CURRENT_ID = CURRENT_ID + 1;
    END
    ELSE
    BEGIN  -- POP THE STACK AND SET RIGHT_KEY VALUE
      UPDATE FIAS$ADDRESSES SET
        NS_ID    = -NS_ID, -- POPS THE STACK
        NS_RIGHT = :COUNTER
      WHERE NS_ID = :CURRENT_ID;
      CURRENT_ID = CURRENT_ID - 1;
    END
  END

  UPDATE FIAS$ADDRESSES SET
    PARENT_FK = NULL
  WHERE PARENT_FK = CHAR_TO_UUID('FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF');
  DELETE FROM FIAS$ADDRESSES
  WHERE OBJECT_ID = CHAR_TO_UUID('FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF');
END^



В таблице FIAS$ADDRESSES порядка 25 000 000 записей. Молотит уже 8 часов, по мне так как-то долго. Куда можно посмотреть? Планы запросов везде вроде адекватные.

P/S FB Classic 2.5.6
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369055
Arioch
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
а может быть попробовать сделать обычную рекурсивную процедуру ?

19804457

при этом все апдейты точечные, но вот читающие курсоры - нет, они сразу на много записей каждый.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369058
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ariochа может быть попробовать сделать обычную рекурсивную процедуру ?

19804457

при этом все апдейты точечные, но вот читающие курсоры - нет, они сразу на много записей каждый.

Такой механизм у меня тоже есть, но он не для всех задач подходит. Для текущей задачи нужен именно Nested Sets.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369068
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__Молотит уже 8 часов, по мне так как-то долго. Куда можно посмотреть?Полностью не вникал, но... я не вижу каким образом может выполнится условие выхода из цикла.
Или все записи посещаются дважды ? Тогда это не оптимально.
Если я правильно понял желаемое и алгоритм, то я бы обходил дерево снизу вверх, а не сверху вниз.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369073
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad__Avenger__Молотит уже 8 часов, по мне так как-то долго. Куда можно посмотреть?Полностью не вникал, но... я не вижу каким образом может выполнится условие выхода из цикла.
Или все записи посещаются дважды ? Тогда это не оптимально.
Если я правильно понял желаемое и алгоритм, то я бы обходил дерево снизу вверх, а не сверху вниз.

Все верно. На каждую запись обход по два раза. На картинке:

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

Взято тут
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369074
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В любом случае 50 000 000 селектов и 50 000 000 обновлений не может идти 10 часов.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369080
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А на запросе
Код: sql
1.
2.
3.
4.
5.
6.
    SELECT FIRST 1 P.OBJECT_ID
    FROM FIAS$ADDRESSES A
    INNER JOIN FIAS$ADDRESSES P ON P.PARENT_FK = A.OBJECT_ID
      AND P.NS_LEFT = 0
    WHERE A.NS_ID = :CURRENT_ID
    INTO :OBJECT_ID;

в разных итерациях план строится заново? Есть у меня одно подозрение.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369082
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__,

план строится для всей процедуры один раз при загрузке её в кеш метаданных.

Вопрос - level уже посчитан и заполнен или нет ?
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369083
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad__Avenger__,

план строится для всей процедуры один раз при загрузке её в кеш метаданных.

Вопрос - level уже посчитан и заполнен или нет ?

Левел в этой таблице это несколько не то, он может в дереве идти 1,3,5,99 для ветки. Так-что нет, не посчитан.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369086
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Прописал жесткий план:
Код: sql
1.
PLAN JOIN (A INDEX (FIAS$ADDRESSES$TMP_NS_ID), P INDEX (FIAS$ADDRESSES_FIAS$ADDRESSES))



И глазам своим не верю
Executing procedure FIAS$BUILD$08... OK (510635 ms, 8,51058 min)

О-о-о-о-о....................
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369103
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__,

я бы с помощью CTE обходил дерево в глубину (у нас рекурсивный CTE иначе и не умеет) и последовательно нумеровал узлы. Есть небольшой нюанс с переходом от листового узла к промежуточному, но это всё решается.

Примерно вот так вот

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
CREATE TABLE T (
    ID         INTEGER NOT NULL,
    PARENT_ID  INTEGER,
    NAME       VARCHAR(16),
    L          INTEGER,
    R          INTEGER
);

ALTER TABLE T ADD CONSTRAINT PK_T PRIMARY KEY (ID);
ALTER TABLE T ADD CONSTRAINT FK_T FOREIGN KEY (PARENT_ID) REFERENCES T (ID);

INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (1, NULL, 'a');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (2, 1, 'aa');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (3, 1, 'ab');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (4, 2, 'aaa');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (5, 2, 'aab');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (6, 3, 'aba');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (7, 3, 'abb');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (8, 6, 'abaa');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (9, 6, 'abab');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (10, 6, 'abac');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (11, 1, 'ac');
INSERT INTO T (ID, PARENT_ID, NAME)       VALUES (12, 1, 'ad');
COMMIT;


CREATE OR ALTER PROCEDURE MAKE_NS
AS
DECLARE N INT = 1;
DECLARE ID INT;
DECLARE PARENT_ID INT;
DECLARE PREV_ID INT;
DECLARE PREV_PARENT INT;
BEGIN
  FOR 
  WITH RECURSIVE
    X AS (
      SELECT ID, PARENT_ID
        FROM T
       WHERE PARENT_ID IS NULL

      UNION ALL

      SELECT T.ID, T.PARENT_ID
        FROM T JOIN X ON T.PARENT_ID = X.ID
    )
  SELECT ID, PARENT_ID
    FROM X
    INTO :ID, :PARENT_ID
  DO BEGIN
    IF (:PREV_ID IS NULL) -- ROOT
    THEN BEGIN
      UPDATE T SET L = :N 
       WHERE ID = :ID;

      N = N + 1; 
      PREV_ID = ID;
      PREV_PARENT = PARENT_ID;
    END
    ELSE IF (:PARENT_ID = PREV_ID) -- STEP DOWN
    THEN BEGIN
      UPDATE T SET L = :N 
       WHERE ID = :ID;

      N = N + 1; 
      PREV_ID = ID;
      PREV_PARENT = PARENT_ID;
    END
    ELSE IF (:PARENT_ID = :PREV_PARENT) -- STEP RIGHT 
    THEN BEGIN
      UPDATE T SET R = :N 
       WHERE ID = :PREV_ID;
       
      UPDATE T SET L = :N + 1
       WHERE ID = :ID;

      N = N + 2; 
      PREV_ID = ID;
      PREV_PARENT = PARENT_ID;
    END
    ELSE BEGIN  -- GO UP AND ASSIGN RIGHT MARKS
      WHILE (:PARENT_ID IS DISTINCT FROM :PREV_PARENT) DO
      BEGIN
        UPDATE T SET R = :N 
         WHERE ID = :PREV_ID
        RETURNING PARENT_ID INTO :PREV_PARENT;

        N = N + 1;
        PREV_ID = PREV_PARENT;
      END

      UPDATE T SET L = :N 
       WHERE ID = :ID;

      N = N + 1; 
      PREV_ID = ID;
      PREV_PARENT = PARENT_ID;
    END
  END

  UPDATE T SET R = :N
   WHERE ID = :ID;

  UPDATE T SET R = :N + 1
   WHERE PARENT_ID IS NULL;
END

...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369170
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad__Avenger__,

я бы с помощью CTE обходил дерево в глубину (у нас рекурсивный CTE иначе и не умеет) и последовательно нумеровал узлы. Есть небольшой нюанс с переходом от листового узла к промежуточному, но это всё решается.


Я думаю нижний блок должен быть таким:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
      WHILE (:PREV_PARENT IS not null) DO
      BEGIN
        UPDATE T SET R = :N 
         WHERE ID = :PREV_ID
        RETURNING PARENT_ID INTO :PREV_PARENT;

        N = N + 1;
        PREV_ID = PREV_PARENT;
      END
  --UPDATE T SET R = :N
   --WHERE ID = :ID;

  --UPDATE T SET R = :N + 1
   --WHERE PARENT_ID IS NULL;
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369190
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__Я думаю нижний блок должен быть такимКоторый за циклом ? Да, похоже на то.

Не пробовал на своих данных ?
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369205
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad__Avenger__Я думаю нижний блок должен быть такимКоторый за циклом ? Да, похоже на то.

Не пробовал на своих данных ?

Сейчас пробую, там много пока других расчетов идет.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369232
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Executing procedure FIAS$BUILD$08... OK (177335 ms, 2,95558 min)

Примерно 3-и минуты.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369299
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну, вроде неплохо :)
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369732
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad,

Спасибо. Все отлично работает, и более рационально.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39369854
rdb_dev
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__, не вижу великого смысла в использовании подобной реализацию с ключами. Ради чего парится с перенумерацией и перепривязкой узлов к ключам порядка при вставке узлов дерева? Ради быстрого получения дочерних узлов вне зависимости от глубины дерева? Если глубина дерева не фантастическая, вполне можно обойтись набором полей id, node_id и seq (порядок элемента в узле), тогда обход дерева прекрасно вырождается в "обход лабиринта по правой стенке" с использованием итерации и не надо городить огород с перепривязкой порядка узлов к ключам порядка. Всё становится гораздо проще и умещается в одну таблицу.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39370034
__Avenger__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rdb_dev__Avenger__, не вижу великого смысла в использовании подобной реализацию с ключами. Ради чего парится с перенумерацией и перепривязкой узлов к ключам порядка при вставке узлов дерева? Ради быстрого получения дочерних узлов вне зависимости от глубины дерева? Если глубина дерева не фантастическая, вполне можно обойтись набором полей id, node_id и seq (порядок элемента в узле), тогда обход дерева прекрасно вырождается в "обход лабиринта по правой стенке" с использованием итерации и не надо городить огород с перепривязкой порядка узлов к ключам порядка. Всё становится гораздо проще и умещается в одну таблицу.

Мне не нужен обход дерева, мне нужен быстрый поиск в дереве.
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39370040
rdb_dev
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__, поиск чего?
...
Рейтинг: 0 / 0
Рассчет дерева Nested Sets
    #39370045
Arioch
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__Avenger__Такой механизм у меня тоже есть, но он не для всех задач подходит. Для текущей задачи нужен именно Nested Sets.

По ссылке и есть NEsted Sets - процедура, которая расставляет L- и R-скобки для всех элементов
...
Рейтинг: 0 / 0
21 сообщений из 21, страница 1 из 1
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Рассчет дерева Nested Sets
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]