Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
9
Добавлен:
15.01.2021
Размер:
14.96 Mб
Скачать

Пример 46: формирование и анализ иерархических структур

GROUP_CONCAT MySQL.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 510/545

Пример 46: формирование и анализ иерархических структур

Переходим к Oracle. Здесь решение будет практически идентичным решению для MS SQL Server. Единственное отличие — в способе возврата таблицы из функции (мы рассматривали этот вопрос ранее, см. решение{355} задачи 5.1.1. b{352}). И здесь мы также вынуждены эмулировать поведение функции MySQL GROUP_CON- CAT через использование функции Oracle LISTAGG (см.

решение{72} задачи

2.2.2. a{7i}).

Oracle

Решение 7.1.1.a (код функции)

1CREATE TYPE "tree_node" AS OBJECT "id" VARCHAR132767));

2/ 3

4CREATE TYPE "nodes_collection" AS TABLE OF "tree_node"

5/ 6

 

CREATE OR REPLACE FUNCTION GET_ALL_CHILDREN(parent_id NUMBER,

8

function_mode VARCHAR)

9RETURN "nodes_collection"

10AS

11result_collection "nodes_collection";

12BEGIN

13IF (function_mode = 'TABLE')

14THEN

15WITH "tree" ("sp_id" "sp_parent")

16AS

17(

18SELECT "sp_id",

19

 

"sp_parent"

20

FROM

"site_pages"

21WHERE "sp_id" = parent_id

22UNION ALL

23SELECT "inner" "sp_id",

24

 

"inner" "sp_parent"

25

FROM

"site_pages" "inner"

26

 

JOIN "tree"

27

 

ON "inner" "sp_parent" = "tree" "sp_id"

28)

29SELECT "tree_node"(TO_CHAR "sp_id"))

30BULK COLLECT INTO result_collection

31 FROM "tree"

32WHERE "sp_id" != parent_id;

33ELSE

34WITH "tree" ("sp_id" "sp_parent")

35AS

36(

37SELECT "sp_id",

38

 

"sp_parent"

39

FROM

"site_pages"

40WHERE "sp_id" = parent_id

41UNION ALL

42SELECT "inner" "sp_id",

43

 

"inner" "sp_parent"

44

FROM

"site_pages" "inner"

45

 

JOIN "tree"

46

 

ON "inner" "sp_parent" = "tree" "sp_id"

47)

48SELECT "tree_node"(LISTAGG(TO_CHAR "sp_id"), ',')

49

 

WITHIN GROUP (ORDER BY "sp_id"))

50

BULK

COLLECT INTO result_collection

51

FROM

"tree"

52WHERE "sp_id" != parent_id;

53END IF;

54RETURN result_collection;

55END;

56/

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 511/545

Пример 46: формирование и анализ иерархических структур

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 512/545

Пример 46: формирование и анализ иерархических структур

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

Oracle і Решение 7.1.1.a (пример использования функции) і

1SELECT . *

2FROM TABLE(CAST(GET_ALL_CHILDREN(2, 'STRING') AS "nodes_collection" );

4SELECT *

FROM TABLE(CAST(GET ALL CHILDRENS, 'TABLE') AS "nodes collection"));

На этом решение данной задачи завершено.

'IT Решение 7.1.1. b{475}.

Легко заметить, что решение этой задачи основано на рассуждениях, представленных в решении{476} задачи 7.1.1.a{475}. Если допустить, что соответствующие функции у нас уже есть, код для всех трёх СУБД будет выглядеть так.

MySQL і

Решение 7.1.1.b (использованием функции GET_ALL_CHILDREN из решения 7.1.1.а)

і

1SELECT . 'sp_id',

2'sp_name'

3

FROM

'site_pages'

4

WHERE

'sp_id' = {родительская_вершина}

 

 

OR FIND IN SET('sp id', GET ALL CHILDREN({родительская вершина}))

 

MS SQL

Решение 7.1.1.b (использованием функции GET_ALL_CHILDREN из решения 7.1.1.a)

1SELECT [sp id],

2[sp name]

3

FROM

[site_pages]

4

WHERE

[sp_id] = {родительская_вершина}

5

 

OR [sp id] IN

6

 

(SELECT [id]

7

 

FROM GET ALL CHILDREN({родительская _вершина}, 'TABLE'))

 

 

 

Oracle

 

Решение 7.1.1.b (использованием функции GET_ALL_CHILDREN из решения 7.1.1

1SELECT.a)"sp id",

2"sp name"

3

FROM

"site_pages"

4

WHERE

"sp_id" = {родительская_вершина}

5

 

OR "sp id" IN

6

 

(SELECT "id"

7

 

FROM TABLE(CAST(

8

 

GET_ALL_CHILDREN({родительская_вершина},

9

 

'TABLE')

10

 

AS "nodes collection" ))

Если же предположить, что функции GET_ALL_CHILDREN у нас нет, решение можно построить следующим образом.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 513/545

Пример 46: формирование и анализ иерархических структур

В MySQL мы берём запрос, вокруг которого построена функция в решении{476} задачи 7.1.1.a{475}, и используем его напрямую как источник списка идентификаторов дочерних страниц.

MySQL і Решение 7.1.1.b

1SELECT 'sp id',

2'sp name'

3

FROM

'site_pages'

4

WHERE

'sp_id' = {родительская_вершина}

5OR FIND IN SET

6('sp id',

7

(SELECT GROUP CONCAT('children ids')

8

FROM

(SELECT 'sp id',

9

 

@parent values :=

10

(

 

11

SELECT GROUP CONCAT('sp id' SEPARATOR ',')

12

FROM

'site_pages'

13

WHERE

FIND IN SET('sp_parent', @parent values > 0

14

) AS 'children ids'

15

FROM

'site_pages'

16

JOIN

(SELECT @parent values := {родительская вершина})

17

 

AS 'initialisation'

18

WHERE

'sp id' IN @parent values)

19) AS 'prepared data')

20)

ВMS SQL Server мы берём рекурсивные общие табличные выражения, вокруг которых построены функции в решении{476} задачи 7.1.1.a{475}, и, добавив в выборку имя страницы (поле sp_name), используем полученный результат как источ-

ник требуемых данных. Здесь даже не придётся исключать из выборки саму родительскую вершину, т.к. по условию данной задачи она должна попадать в конечный набор данных.

Обратите внимание на тот факт, что MS SQL Server допускает создание рекурсивных общих табличных выражений без явного указания списка колонок, в то время как в Oracle этот список обязателен.

MS SQL Решение 7.1.1.b

1WITH [tree]

2AS

3(

4SELECT [sp_id],

5[sp_parent],

6

 

[sp_name]

7

FROM

[site_pages]

8

WHERE

[sp_id] = {родительская_вершина}

9UNION ALL

10SELECT [inner] [sp_id],

11[inner].[sp_parent],

12

 

[inner] [sp_name]

13

FROM

[site_pages] AS [inner]

14JOIN [tree]

15ON [inner] [sp_parent] = [tree]

16)

17SELECT [sp_id],

18[sp_name]

19FROM [tree]

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 514/545

Пример 46: формирование и анализ иерархических структур

Oracle I Решение 7.1.1. b

1WITH "tree" "sp_id", "sp_parent", "sp_name")

2AS

3(

4SELECT "sp id"

5"sp_parent"

6"sp name"

7

FROM

"site_pages"

8

WHERE

"sp_id" = {родительская_вершина}

9UNION ALL

10SELECT "inner" "sp id"

11"inner" "sp_parent"

12"inner" "sp name"

13

FROM

"site_pages" "inner"

14

 

JOIN "tree"

15

 

ON "inner" "sp_parent" = "tree" "sp_id"

16)

17SELECT "sp id",

18"sp name"

19FROM "tree"

Если же отойти от традиции, в рамках которой мы рассматриваем во всех трёх СУБД максимально похоже решения, то в Oracle данную задачу можно решить с помощью конструкции CONNECT BY.

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

 

Oracl

і

Решение 7.1.1.b (альтернативный вариант)

|

e

 

 

 

 

 

1

 

SELECT "sp id",

 

2

 

 

"sp_parent",

 

3"sp_name",

4LEVEL

5

FROM

"si te_pages"

6START WITH "sp id" = {родительская вершина}

7CONNECT BY PRIOR "sp_id" = "sp_parent"

Для страницы с идентификатором 2, этот запрос вернёт следующие данные.

sp_id

sp_parent

sp_name

LEVEL

2

1

Читателям

1

5

2

Новости

2

6

2

Статистика

2

12

6

Текущая

3

13

6

Архивная

3

14

6

Неофициальная

3

Но и это ещё не всё: функция SYS_CONNECT_BY_PATH позволяет для каждой

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

 

Oracl

і

Решение 7.1.1.b (альтернативный вариант)

|

e

 

 

 

 

 

1

 

SELECT LPAD(' ', 2 * LEVEL, ' ') || "sp name" "debug".

2

 

 

"sp_id",

 

3"sp_parent".

4"sp name",

5SYS CONNECT BY PATH("sp name", '/') "path with names",

6

 

SYS CONNECT BY PATH("sp id" ',') "path with ids"

7

FROM

"si te_pages"

8START WITH "sp_id" = {родительская_вершина}

9CONNECT BY PRIOR "sp_id" = "sp_parent"

10ORDER SIBLINGS BY "sp_name"

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 515/545

Пример 46: формирование и анализ иерархических структур

Для страницы с идентификатором 2, этот запрос вернёт следующие данные.

debug

sp_id

sp_par-

sp_name

path_with_names

path_with_ids

 

 

ent

 

 

 

Читателям

2

1

Читателям

/Читателям

,2

Новости

5

2

Новости

/Читателям/Новости

,2,5

 

6

2

Статистика

/Читателям/Статистика

,2,6

Статистика

 

 

 

 

 

Архивная

13

6

Архивная

/Читателям/Статистика/Ар-

,2,6,13

 

 

 

 

хивная

 

Неофи-

14

6

Неофици-

/Читателям/Статистика/Не-

,2,6,14

циальная

 

 

альная

официальная

 

Текущая

12

6

Текущая

/Читателям/Статистика/Теку-

,2,6,12

 

 

 

 

щая

 

На этом решение данной задачи завершено.

ЧР’ Решение 7.1.1. С{475}.

В MySQL нет рекурсивных запросов, нет возможности вернуть из функции таблицу, нет общих табличных выражений — остаётся использовать алгоритм с циклом, в котором на каждом шаге мы поднимаемся на один уровень иерархии вверх, пока не достигнем корневой вершины (ссылка на родителя равна NULL).

Здесь мы последовательно подменяем (строки 12-15) значение идентификатора текущего узла и накапливаем (строки 16-19) все полученные значения в строковой переменной, которая и является результатом работы функции.

Обратите внимание, что MySQL допускает указание RETURN прямо в объявлении обработчика ситуации «запрос вернул пустой результат» (строка 7), и таким образом нет необходимости явно делать RETURN далее в коде функции.

MySQL

Решение 7.1.1.С (код

1DELIMITERфункции$$ )

2CREATE FUNCTION GET PATH TO ROOT start node INT) RETURNS TEXT

3NOT DETERMINISTIC

4BEGIN

5DECLARE path to root TEXT;

6DECLARE current node INT;

7DECLARE EXIT HANDLER FOR NOT FOUND RETURN path_to_root;

8

 

 

9

SET current node = start node

10

SET path to root = start node

11

LOOP

 

12

SELECT

'sp_parent'

13

INTO

current node

14

FROM

'site_pages'

15

WHERE

'sp id' = current node;

16IF (current node IS NOT NULL)

17THEN

18SET path to root = CONCAT(path to root ',', current node ;

19END IF;

20END LOOP;

21END$$

22DELIMITER ;

Использовать полученную функцию можно так.

MySQL Решение 7.1.1.C (пример использования функции)

1 SELECT GET_PATH_TO_ROOT(141

На этом решение для MySQL завершено.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 516/545

Пример 46: формирование и анализ иерархических структур

Переходим к MS SQL Server. Поскольку в данной СУБД есть возможность возвратить из функции результат в виде таблицы, мы реализуем оба варианта поведения — и возврат таблицы, и возврат строки.

Встроках 9-17 мы реализуем такой же алгоритм, как и в решении для MySQL, но все рассмотренные значения идентификаторов вершин не накапливаем в строку,

апомещаем в результирующую таблицу.

Встроках 19-28 мы проверяем необходимость вернуть результат в виде набора идентификаторов, разделённых запятыми и, если такая необходимость есть, собираем все данные из результирующей таблицы в строку (строки 21-24 кода), очищаем результирующую таблицу (строка 25 кода) и помещаем в неё полученную строку (строки 26-27 кода).

MS SQL і

Решение 7.1.1.С (код функции)

1CREATE FUNCTION GET PATH TO ROOT @current node INT, @mode VARCHAR(50 )

2RETURNS @path TABLE

3(

4id VARCHAR(max)

5)

6AS

7BEGIN

8DECLARE @all as string VARCHAR(max) = '';

9WHILE (@current node IS NOT NULL)

10BEGIN

11INSERT INTO @path

12SELECT CAST @current node AS VARCHAR);

13

 

 

14

SET @current_node = (SELECT [sp_parent]

15

FROM

[site_pages]

16

WHERE

[sp id] = @current node ;

17

END;

 

18

 

 

19IF @mode = 'STRING')

20BEGIN

21SET @all as string = (SELECT STUFF((SELECT ',' + CAST([id] AS VARCHAR)

22

FROM

@path

23

FOR XML PATH(''), TYPE) value ('.', 'nvarchar(max) '),

24

1, 1,

''));

25DELETE FROM @path;

26INSERT INTO @path

27SELECT @all as string

28END;

29

30RETURN

31END;

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

MS SQL Решение 7.1.1.С (пример использования функции)

1 SELECT * FROM GET_PATH_TO_ROOT 14 'TABLE')

2 SELECT * FROM GET PATH TO

ROOT 14 'STRING');

Поскольку MS SQL Server поддерживает рекурсивные общие табличные выражения, эту задачу можно решить также с их помощью. Для краткости мы не будем создавать отдельную функцию, но приведём запрос, который даже сам по себе возвращает именно такой результат, какой требуется по условию задачи.

JOIN в строке 11 как раз и обеспечивает необходимое нам рекурсивное поведение.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 517/545

Пример 46: формирование и анализ иерархических структур

MS SQL I

Решение 7.1.1.c (альтернативный вариант)

|

1

WITH [path_to_root] AS

 

2

(

 

 

 

3

 

SELECT [sp id],

 

4

 

 

[sp_parent]

 

5

 

FROM

[site_pages]

 

6

 

WHERE

[sp_id] = {исходная_вершина}

7

 

UNION ALL

 

8

 

SELECT [inner] [sp id],

 

9

 

 

[inner] [sp_parent]

 

10

 

FROM

[site_pages] AS [inner]

 

11

 

JOIN [path to root] ON [path to root] [sp_parent] = [inner] [sp_id]

12

)

 

 

 

13

SELECT [sp__id] FROM [path to root]

 

 

 

 

 

На этом решение для MS SQL Server завершено.

Переходим к Oracle и реализуем решение по аналогии с MS SQL Server.

Oracle і

Решение 7.1.1.С (код функции)

і

1CREATE TYPE "рЪг_Ьгёё“поаё" AS OBJECT ("id" VARCHAR (32767) ) ;

2/ 3

4CREATE TYPE "ptr_nodes_collection" AS TABLE OF "ptr_tree_node";

5/ 6

CREATE OR REPLACE FUNCTION GET_PATH_TO_ROOT start_id NUMBER,

8

function_mode VARCHAR)

9RETURN "ptr_nodes_collection"

10AS

11result_collection "ptr_nodes_collection" := "ptr_nodes_collection" );

12all_as_string VARCHAR 32767 ;

13temp_int_value NUMBER 10 ;

14BEGIN

15temp_int_value := start_id; 16

17WHILE temp_int_value IS NOT NULL)

18LOOP

19IF (function_mode = 'TABLE')

20THEN

21result_collection extend

22result_collection result_collection last :=

23

"ptr_tree_node" (TO_CHAR(temp_int_value );

24

ELSE

25

all_as_string := all_as_string || ',' || temp_int_value

26END IF;

27SELECT "sp_parent" INTO temp_int_value

28FROM "site_pages"

29WHERE "sp_id" = temp_int_value

30END LOOP; 31

32 all_as_string := SUBSTR(all_as_string, 2); 33

34IF (function_mode != 'TABLE')

35THEN

36SELECT "ptr_tree_node" TO_CHAR all_as_stringl)

37BULK COLLECT INTO result_collection

38FROM DUAL;

39END IF; 40

41RETURN result_collection

42END;

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 518/545

Пример 46: формирование и анализ иерархических структур

Поскольку в Oracle нельзя присваивать новое значение входному параметру функции, мы используем временную переменную (строки 13 и 15) для хранения значений идентификаторов вершин и использования в цикле.

Вотличие от MS SQL Server, где внутри функции у нас есть полноценная таблица для хранения данных, в Oracle мы используем коллекцию, потому для упрощения кода мы сразу в теле цикла проверяем, придётся ли нам возвратить колонку идентификаторов (тогда мы добавляем их в коллекцию) или строку с их перечислением (тогда мы накапливаем их в строковой переменной).

Встроках 34-39 мы снова проверяем режим работы функции и помещаем строку с перечислением идентификаторов (из которой в строке 32 убираем первую лишнюю запятую) в коллекцию (которая сейчас пуста, если функция вызвана в режиме возврата перечисления идентификаторов).

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

Oracle

і Решение 7.1.1.С (пример использования функции) SELECT

*

1

2

FROM

 

TABLE(CAST(GET_PATH_TO_ROOT(14, 'TABLE') AS

"ptr_nodes_collection" );

3

 

 

4

SELECT *

 

 

FROM TABLE(CAST(GET PATH TO ROOT(14, 'STRING') AS "ptr nodes collection" );

Поскольку Oracle (как и MS SQL Server) поддерживает рекурсивные общие табличные выражения, эту задачу можно решить также с их помощью. Для краткости мы не будем создавать отдельную функцию, но приведём запрос, который даже сам по себе возвращает именно такой результат, какой требуется по условию задачи.

 

Oracl

і

Решение 7.1.1.c (альтернативный вариант)

|

 

e

 

 

 

 

 

 

 

 

1

 

WITH "path_to_root"

"sp_id", "sp_parent"

AS

2

 

(

 

 

 

 

3

 

SELECT "sp id"

 

 

 

4

 

"sp_parent"

5

FROM

"site_pages"

6

WHERE

"sp_id" = {исходная_вершина}

7UNION ALL

8SELECT "inner" "sp id"

9"inner" "sp_parent"

10FROM "site_pages" "inner"

11JOIN "path to root" ON "path to root" "sp_parent" = "inner" "sp id"

12)

13SELECT "sp_ _id" FROM "path _to_ root"

Как и в решении задачи 7.1.1. b, мы рассмотрим ещё один вариант, доступный только в Oracle. На основе выражения CONNECT BY и функции SYS_CON- NECT_BY_PATH очень легко получается решение с представлением результата в виде строки идентификаторов.

 

Oracl

і

Решение 7.1.1.c (альтернативный вариант)

|

 

e

 

 

 

 

 

 

 

 

 

1

 

SELECT SUBSTR(SYS CONNECT BY PATH "sp id", ','), 2

"path with ids"

2

 

FROM

"si te_pages"

 

 

 

 

3

 

WHERE

"sp id" = {исходная вершина}

 

 

4

 

START WITH "sp id" = (SELECT "sp id"

 

 

5

 

 

FROM

"site_pages"

 

6

 

 

WHERE

"sp_parent" IS NULL)

 

7

 

CONNECT BY PRIOR "sp_id" = "sp_parent"

 

8

 

ORDER SIBLINGS BY "sp_name"

 

 

 

 

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016-2018 Стр: 519/545