LEVEL в Oracle SQL или как узнать уровень вложенности в иерархии

🟢 LEVEL в Oracle SQL. Введение

LEVEL — псевдостолбец иерархических запросов в Oracle SQL. Он показывает глубину текущей строки в дереве (корень = 1) при использовании CONNECT BY и START WITH.
Кроме обхода иерархий, трюк CONNECT BY с LEVEL применяют как «генератор строк» — для построения числовых и календарных серий, развертки интервалов и т. п.

Где используют

  • Иерархии: корни, листья, ограничение глубины, визуализация дерева, агрегаты по поддеревьям.
  • Серии значений: числа 1..N, диапазоны дат/часов, искусственные ключи для тестовых наборов.
  • Развертка интервалов: детализировать «с начала по конец» по дням/месяцам.
  • Эксплоды: дублировать строки по количеству (explode), строить временные сетки.

Синтаксис

-- Иерархия
SELECT ..., LEVEL
FROM t
START WITH root_condition
CONNECT BY PRIOR parent_id = child_id;

-- Генератор строк 1..N
SELECT LEVEL AS n
FROM dual
CONNECT BY LEVEL <= :N;
  • LEVEL доступен в списке SELECT, предикатах и группировке.
  • Сортировку внутри ветвей выполняйте через ORDER SIBLINGS BY.
  • Для ограничения глубины фильтруйте WHERE LEVEL <= k (помните, что это фильтр после построения дерева).

100 примеров

1. Вывести оргструктуру с отступами

SELECT
  LPAD(' ', (LEVEL-1)*2) || emp_name AS emp_name,
  emp_id, manager_id, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY emp_name;

2. Фильтр максимум до трёх уровней

SELECT emp_id, emp_name, LEVEL
FROM employees
WHERE LEVEL <= 3
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

3. Только листья дерева (признак CONNECT_BY_ISLEAF)

SELECT emp_id, emp_name
FROM employees
WHERE CONNECT_BY_ISLEAF = 1
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

4. Корневое значение для узла (CONNECT_BY_ROOT)

SELECT emp_id, CONNECT_BY_ROOT emp_id AS root_id, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

5. Путь «корень → узел» с SYS_CONNECT_BY_PATH

SELECT emp_id,
       SYS_CONNECT_BY_PATH(emp_name, ' / ') AS path
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

6. Стабильная сортировка соседей

SELECT emp_id, emp_name, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY emp_name;

7. Подсчёт узлов на каждом уровне

SELECT LEVEL, COUNT(*) AS cnt
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
GROUP BY LEVEL
ORDER BY LEVEL;

8. Только заданный уровень глубины

SELECT emp_id, emp_name, LEVEL
FROM employees
WHERE LEVEL = 2
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

9. Количество листьев по корням

SELECT CONNECT_BY_ROOT emp_id AS root_id,
       SUM(CASE WHEN CONNECT_BY_ISLEAF = 1 THEN 1 ELSE 0 END) AS leaf_cnt
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
GROUP BY CONNECT_BY_ROOT emp_id;

10. Числовой ряд 1..10 с генератором строк

SELECT LEVEL AS n
FROM dual
CONNECT BY LEVEL <= 10;

11. Календарь: дни месяца 1..31

SELECT (DATE '2025-01-01' + LEVEL - 1) AS dt
FROM dual
CONNECT BY LEVEL <= 31;

12. Генерация почасового ряда за сутки

SELECT (TRUNC(SYSTIMESTAMP, 'DD') + NUMTODSINTERVAL(LEVEL-1,'HOUR')) AS ts
FROM dual
CONNECT BY LEVEL <= 24;

13. Серия чисел и JOIN c таблицей

SELECT t.id, s.n
FROM t
CROSS JOIN (
  SELECT LEVEL AS n FROM dual CONNECT BY LEVEL <= 5
) s;

14. Развёртка интервалов по дням

SELECT id, (start_date + LEVEL - 1) AS day_dt
FROM periods
CONNECT BY PRIOR id = id
       AND PRIOR SYS_GUID() IS NOT NULL
       AND LEVEL <= (end_date - start_date + 1);

15. Дублирование строк по количеству (explode)

SELECT o.order_id, o.product_id
FROM orders o
CONNECT BY PRIOR order_id = order_id
   AND PRIOR SYS_GUID() IS NOT NULL
   AND LEVEL <= o.qty;

16. Иерархия категорий с подсветкой глубины

SELECT
  LPAD('-', LEVEL*2, '-') || cat_name AS titled_name
FROM categories
START WITH parent_id IS NULL
CONNECT BY PRIOR cat_id = parent_id;

17. Предки узла: обратный подъём

SELECT emp_id, manager_id, LEVEL
FROM employees
START WITH emp_id = :leaf_id
CONNECT BY emp_id = PRIOR manager_id;

18. Подсчёт потомков через подзапрос

SELECT e.emp_id,
       (SELECT COUNT(*)
        FROM employees
        START WITH emp_id = e.emp_id
        CONNECT BY PRIOR emp_id = manager_id
       ) AS descendants
FROM employees e;

19. Первые N уровней для визуализации

SELECT emp_id, emp_name, LEVEL
FROM employees
WHERE LEVEL <= :max_depth
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

20. Разделитель блоков иерархии по отделам

SELECT dept_id, emp_id, emp_name, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY dept_id, emp_name;

Еще 20 примеров.

21. Серия дат между границами (на каждую запись)

SELECT id, (date_from + LEVEL - 1) AS d
FROM ranges
CONNECT BY PRIOR id = id
   AND PRIOR SYS_GUID() IS NOT NULL
   AND LEVEL <= (date_to - date_from + 1);

22. Серия месяцев (первое число каждого)

SELECT ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), LEVEL-1) AS month_start
FROM dual
CONNECT BY LEVEL <= 12;

23. Счёт узлов на глубине ≥ 3

SELECT COUNT(*) AS deep_nodes
FROM employees
WHERE LEVEL >= 3
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

24. Формирование ключа «root:node:depth»

SELECT CONNECT_BY_ROOT emp_id || ':' || emp_id || ':' || LEVEL AS key
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

25. Сортировка ветвей, затем глобальная сортировка

SELECT *
FROM (
  SELECT emp_id, emp_name, LEVEL
  FROM employees
  START WITH manager_id IS NULL
  CONNECT BY PRIOR emp_id = manager_id
  ORDER SIBLINGS BY emp_name
)
ORDER BY LEVEL, emp_name;

26. Маркировка циклов с NOCYCLE

SELECT emp_id, manager_id, CONNECT_BY_ISCYCLE AS cyc, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY NOCYCLE PRIOR emp_id = manager_id;

27. Фильтр ветвей по статусу родителя

SELECT emp_id, emp_name, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
   AND PRIOR status = 'ACTIVE';

28. Только корни (глубина = 1)

SELECT emp_id, emp_name, LEVEL
FROM employees
WHERE LEVEL = 1
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

29. Только третий уровень

SELECT emp_id, emp_name
FROM employees
WHERE LEVEL = 3
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

30. Идентификатор корня по узлу

SELECT emp_id, CONNECT_BY_ROOT emp_id AS root_id, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

31. Нумерация узлов в пределах корня

SELECT
  CONNECT_BY_ROOT emp_id AS root_id,
  ROW_NUMBER() OVER (PARTITION BY CONNECT_BY_ROOT emp_id ORDER BY LEVEL) AS rn,
  emp_id, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

32. Листья с путями

SELECT SYS_CONNECT_BY_PATH(emp_name, ' / ') AS path
FROM employees
WHERE CONNECT_BY_ISLEAF = 1
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

33. Только подчинённые (без корней)

SELECT emp_id, emp_name
FROM employees
WHERE LEVEL > 1
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

34. Формирование календарной сетки неделю вперёд

SELECT TRUNC(SYSDATE) + LEVEL - 1 AS dt
FROM dual
CONNECT BY LEVEL <= 7;

35. Серия часов внутри даты

SELECT TRUNC(SYSDATE) + (LEVEL-1)/24 AS hour_ts
FROM dual
CONNECT BY LEVEL <= 24;

36. Серия минут на 15‑минутной сетке

SELECT TRUNC(SYSDATE) + NUMTODSINTERVAL((LEVEL-1)*15,'MINUTE') AS ts
FROM dual
CONNECT BY LEVEL <= 96;

37. Расширение периодов по минутам (пример)

SELECT id, start_ts + NUMTODSINTERVAL(LEVEL-1,'MINUTE') AS ts
FROM intervals
CONNECT BY PRIOR id = id
   AND PRIOR SYS_GUID() IS NOT NULL
   AND LEVEL <= (end_ts - start_ts) * 24*60 + 1;

38. Развертка количества в заказах по единицам

SELECT order_id, product_id, LEVEL AS unit_no
FROM order_items
CONNECT BY PRIOR order_item_id = order_item_id
   AND PRIOR SYS_GUID() IS NOT NULL
   AND LEVEL <= quantity;

39. Дата‑серия между двумя параметрами

SELECT (DATE :d1 + LEVEL - 1) AS d
FROM dual
CONNECT BY LEVEL <= (:d2 - :d1 + 1);

40. Серия для тестовых идентификаторов

SELECT 'USR' || TO_CHAR(LEVEL, 'FM0000') AS user_code
FROM dual
CONNECT BY LEVEL <= 100;

Еще 20 примеров.

41. Свод по уровням глубины

SELECT LEVEL, SUM(salary) AS total_sal
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
GROUP BY LEVEL
ORDER BY LEVEL;

42. Отсечь ветви глубже заданной

SELECT emp_id, emp_name
FROM employees
WHERE LEVEL <= :k
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

43. Вложенная иерархия: отделы → сотрудники

SELECT d.dept_name, e.emp_name, LEVEL
FROM departments d
JOIN employees e ON e.dept_id = d.dept_id
START WITH d.parent_id IS NULL
CONNECT BY PRIOR d.dept_id = d.parent_id;

44. Иерархия категорий с фильтром листьев

SELECT cat_id, cat_name, LEVEL
FROM categories
START WITH parent_id IS NULL
CONNECT BY PRIOR cat_id = parent_id
HAVING COUNT(*) >= 0;

45. Пути идентификаторов через SYS_CONNECT_BY_PATH

SELECT cat_id, SYS_CONNECT_BY_PATH(cat_id, '/') AS id_path
FROM categories
START WITH parent_id IS NULL
CONNECT BY PRIOR cat_id = parent_id;

46. Сортировка siblings по имени, затем по id

SELECT emp_id, emp_name, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY emp_name, emp_id;

47. Серия рабочих дней (без выходных)

SELECT d
FROM (
  SELECT TRUNC(SYSDATE, 'IW') + LEVEL - 1 AS d
  FROM dual
  CONNECT BY LEVEL <= 7
)
WHERE TO_CHAR(d,'DY','NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('SAT','SUN');

48. Серия месяцев назад на год

SELECT ADD_MONTHS(TRUNC(SYSDATE,'MM'), -(LEVEL-1)) AS month_start
FROM dual
CONNECT BY LEVEL <= 12;

49. Суммарная глубина дерева

SELECT SUM(LEVEL) AS depth_sum
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

50. Максимальная глубина через агрегат

SELECT MAX(LEVEL) AS max_depth
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

51. Подсветка глубины в отчёте

SELECT RPAD('*', LEVEL, '*') AS stars, emp_name
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

52. Иерархия файловой системы

SELECT path, LEVEL
FROM files
START WITH parent_id IS NULL
CONNECT BY PRIOR file_id = parent_id;

53. Выборка ровно на втором уровне по условию

SELECT emp_id, emp_name
FROM employees
WHERE LEVEL = 2 AND dept_id = :d
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

54. Серия для разметки часов работы

SELECT TO_CHAR(TRUNC(SYSDATE) + (LEVEL-1)/24,'HH24:MI') AS hh
FROM dual
CONNECT BY LEVEL <= 24;

55. Построение шкалы процентов 0..100

SELECT LEVEL-1 AS pct
FROM dual
CONNECT BY LEVEL <= 101;

56. Счетчик для распределения по корзинам

SELECT CASE WHEN MOD(LEVEL,2)=0 THEN 'even' ELSE 'odd' END AS bucket, COUNT(*) cnt
FROM dual
CONNECT BY LEVEL <= 100
GROUP BY CASE WHEN MOD(LEVEL,2)=0 THEN 'even' ELSE 'odd' END;

57. Серия дат с шагом 7 дней

SELECT (DATE '2025-01-01' + 7*(LEVEL-1)) AS week_start
FROM dual
CONNECT BY LEVEL <= 8;

58. Валидатор глубины: только до 5 уровней

SELECT COUNT(*) AS ok_rows
FROM employees
WHERE LEVEL <= 5
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

59. Подсчёт узлов ниже целевого корня

SELECT COUNT(*) AS cnt
FROM employees
START WITH emp_id = :root
CONNECT BY PRIOR emp_id = manager_id;

60. Серия для теста производительности

EXPLAIN PLAN FOR
SELECT /* level test */ * FROM dual CONNECT BY LEVEL <= 10000;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());

Еще 20 примеров.

61. Глубина для каждого узла и корень

SELECT emp_id, CONNECT_BY_ROOT emp_id AS root_id, LEVEL AS depth
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

62. Серия для генерации случайных данных

SELECT DBMS_RANDOM.STRING('U', 8) AS s
FROM dual
CONNECT BY LEVEL <= 100;

63. Серия для пересечения с календарём

SELECT cal.d
FROM calendar cal
JOIN (SELECT TRUNC(SYSDATE) + LEVEL - 1 AS d FROM dual CONNECT BY LEVEL <= 30) s
  ON cal.d = s.d;

64. Серия по количеству дней в месяце

SELECT TRUNC(ADD_MONTHS(SYSDATE,1),'MM') - TRUNC(SYSDATE,'MM') AS days_in_month FROM dual;
SELECT TRUNC(SYSDATE,'MM') + LEVEL - 1 AS d
FROM dual
CONNECT BY LEVEL <= (TRUNC(ADD_MONTHS(SYSDATE,1),'MM') - TRUNC(SYSDATE,'MM'));

65. Серия id для выборки первых N строк

SELECT * FROM (
  SELECT t.*, LEVEL AS rn
  FROM t
  CONNECT BY LEVEL <= :n
) WHERE rn <= :n;

66. Серия номеров страниц (пагинация)

SELECT LEVEL AS page_no
FROM dual
CONNECT BY LEVEL <= CEIL(:total / :per_page);

67. Глубина и число потомков в одной выборке

SELECT e.emp_id, LEVEL AS depth,
       (SELECT COUNT(*) FROM employees START WITH emp_id = e.emp_id CONNECT BY PRIOR emp_id = manager_id) AS subcnt
FROM employees e
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

68. Расширение интервалов по месяцам

SELECT id, ADD_MONTHS(start_month, LEVEL-1) AS m
FROM months_range
CONNECT BY PRIOR id = id
   AND PRIOR SYS_GUID() IS NOT NULL
   AND LEVEL <= MONTHS_BETWEEN(end_month, start_month) + 1;

69. Серия для теста индекса по дате

SELECT TRUNC(SYSDATE) + LEVEL - 1 AS d
FROM dual
CONNECT BY LEVEL <= 365;

70. Глубина и путь для BOM

SELECT parent_part, child_part, LEVEL,
       SYS_CONNECT_BY_PATH(child_part,'/') AS path
FROM bom
START WITH parent_part = :p
CONNECT BY PRIOR child_part = parent_part;

71. Ограничение глубины и фильтр листьев

SELECT child_part, LEVEL
FROM bom
WHERE LEVEL <= 3 AND CONNECT_BY_ISLEAF = 1
START WITH parent_part = :p
CONNECT BY PRIOR child_part = parent_part;

72. Серия кварталов текущего года

SELECT ADD_MONTHS(TRUNC(SYSDATE,'YYYY'), (LEVEL-1)*3) AS q_start
FROM dual
CONNECT BY LEVEL <= 4;

73. Серия номеров недель ISO

SELECT TO_NUMBER(TO_CHAR(TRUNC(SYSDATE,'IYYY') + (LEVEL-1)*7,'IW')) AS iw
FROM dual
CONNECT BY LEVEL <= 53;

74. Серия для sample‑данных пользователей

SELECT 'user_' || LEVEL AS uname
FROM dual
CONNECT BY LEVEL <= 20;

75. Глубина узлов отделов и сортировка

SELECT dept_id, dept_name, LEVEL
FROM departments
START WITH parent_id IS NULL
CONNECT BY PRIOR dept_id = parent_id
ORDER SIBLINGS BY dept_name;

76. Серия с шагом 0.5 дня

SELECT TRUNC(SYSDATE) + (LEVEL-1)*0.5 AS halfday
FROM dual
CONNECT BY LEVEL <= 10;

77. Серия с шагом в минутах (5м)

SELECT TRUNC(SYSDATE) + NUMTODSINTERVAL((LEVEL-1)*5,'MINUTE') AS ts
FROM dual
CONNECT BY LEVEL <= 12;

78. Серия дней для каждой записи логов (пример)

SELECT l.id, TRUNC(l.ts) + LEVEL - 1 AS d
FROM logs l
CONNECT BY PRIOR id = id
   AND PRIOR SYS_GUID() IS NOT NULL
   AND LEVEL <= :days;

79. Глубина в треде комментариев и порядок «соседей»

SELECT LPAD(' ', (LEVEL-1)*2) || author || ': ' || text AS thread_line, created_at
FROM comments
START WITH parent_id IS NULL
CONNECT BY PRIOR comment_id = parent_id
ORDER SIBLINGS BY created_at;

80. Серия идентификаторов для тестовых вставок

SELECT LEVEL AS id FROM dual CONNECT BY LEVEL <= 100;

Еще 20 примеров.

81. Серия часов для сводной таблицы

SELECT TO_CHAR(TRUNC(SYSDATE) + (LEVEL-1)/24,'HH24') AS hh
FROM dual
CONNECT BY LEVEL <= 24;

82. Глубина и количество узлов на глубине

SELECT LEVEL, COUNT(*) cnt
FROM categories
START WITH parent_id IS NULL
CONNECT BY PRIOR cat_id = parent_id
GROUP BY LEVEL
ORDER BY LEVEL;

83. Пути и глубина для категорий

SELECT cat_id, SYS_CONNECT_BY_PATH(cat_name,' > ') AS p, LEVEL
FROM categories
START WITH parent_id IS NULL
CONNECT BY PRIOR cat_id = parent_id;

84. Серия дат от первого дня месяца

SELECT TRUNC(SYSDATE,'MM') + LEVEL - 1 AS d
FROM dual
CONNECT BY LEVEL <= TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE),'DD'));

85. Серия номеров кварталов 1..4

SELECT LEVEL AS q FROM dual CONNECT BY LEVEL <= 4;

86. Серия шагов 10..100 с шагом 10

SELECT LEVEL*10 AS step FROM dual CONNECT BY LEVEL <= 10;

87. Серия для «топ‑N» имитации

SELECT * FROM (
  SELECT t.*, LEVEL AS rn
  FROM t
  CONNECT BY LEVEL <= :n
) WHERE rn <= :n;

88. Свод глубины по корню

SELECT CONNECT_BY_ROOT emp_id AS root_id, MAX(LEVEL) AS depth
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
GROUP BY CONNECT_BY_ROOT emp_id;

89. Серия дат «вчера..сегодня..завтра»

SELECT TRUNC(SYSDATE) + LEVEL - 2 AS d
FROM dual
CONNECT BY LEVEL <= 3;

90. Серия букв через CHR

SELECT CHR(64 + LEVEL) AS letter
FROM dual
CONNECT BY LEVEL <= 26;

91. Серия для генерации пар (i,j)

SELECT a.n AS i, b.n AS j
FROM (SELECT LEVEL AS n FROM dual CONNECT BY LEVEL <= 3) a
CROSS JOIN (SELECT LEVEL AS n FROM dual CONNECT BY LEVEL <= 3) b;

92. Серия для распределения по сменам 1..3

SELECT MOD(LEVEL-1,3)+1 AS shift_no, COUNT(*) cnt
FROM dual
CONNECT BY LEVEL <= 30
GROUP BY MOD(LEVEL-1,3)+1;

93. Серия по дням вокруг даты на 15 дней

SELECT TRUNC(:d) + LEVEL - 16 AS d
FROM dual
CONNECT BY LEVEL <= 31;

94. Вывести оргструктуру с отступами

SELECT
  LPAD(' ', (LEVEL-1)*2) || emp_name AS emp_name,
  emp_id, manager_id, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY emp_name;

95. Фильтр максимум до трёх уровней

SELECT emp_id, emp_name, LEVEL
FROM employees
WHERE LEVEL <= 3
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

96. Только листья дерева (признак CONNECT_BY_ISLEAF)

SELECT emp_id, emp_name
FROM employees
WHERE CONNECT_BY_ISLEAF = 1
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

97. Корневое значение для узла (CONNECT_BY_ROOT)

SELECT emp_id, CONNECT_BY_ROOT emp_id AS root_id, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

98. Путь «корень → узел» с SYS_CONNECT_BY_PATH

SELECT emp_id,
       SYS_CONNECT_BY_PATH(emp_name, ' / ') AS path
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id;

99. Стабильная сортировка соседей

SELECT emp_id, emp_name, LEVEL
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY emp_name;

100. Подсчёт узлов на каждом уровне

SELECT LEVEL, COUNT(*) AS cnt
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
GROUP BY LEVEL
ORDER BY LEVEL;

Частые ошибки и подводные камни

  1. Где писать ограничение глубины. Условие по LEVEL в WHERE режет результат после построения. Иногда лучше ограничивать ветви дополнительными условиями в CONNECT BY.
  2. Неправильное направление связи. Проверьте, где стоит PRIOR, иначе уровни «перевернутся».
  3. Порядок внутри ветвей. Используйте ORDER SIBLINGS BY; обычный ORDER BY перемешает ветви.
  4. Производительность. Глубокие/широкие деревья растут экспоненциально — добавляйте селективные предикаты и лимиты глубины.

Альтернативы

  • Рекурсивный WITH на UNION ALL — гибче для нетривиальных правил обхода.
  • Серии без иерархий — через календарные таблицы или генераторы (внешние источники данных).

Заключение

LEVEL удобен как для иерархий, так и для генерации рядов. Контролируйте глубину, сортировку «соседей» и используйте проверенные шаблоны для серий чисел и дат.

Документация

Oracle SQL Language Reference — Hierarchical Queries

LEVEL, CONNECT_BY_ROOT, CONNECT_BY_ISLEAF, SYS_CONNECT_BY_PATH


🔜 Следующая статья:

PRIOR в Oracle SQL: как правильно связать родителя и потомка в иерархических запросах


Понравилась статья? Поделиться с друзьями: