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

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

Единственная особенность заключается в последовательности размещения идентификаторов: во всех ранее рассмотренных вариантах корневая вершина находилась справа (например, для вершины 14 последовательность была 14,6,2,1), а здесь корневая вершина будет находиться слева (т.е. получится 1,2,6,14).

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

Задание 7.1.1.TSK.A: создать функцию, возвращающую список идентификаторов всех дочерних вершин заданной вершины (например, идентификаторов всех подстраниц страницы «Читателям») на глубину, не более

заданной.

Задание 7.1.1.TSK.B: написать запрос для показа всего поддерева заданной вершины дерева, включая саму родительскую вершину (например, всех подстраниц страницы «Читателям», включая саму эту страницу), в котором с каждого уровня иерархии в выборку попадает не более одной вершины.

Задание 7.1.1.TSK.C: написать функцию, возвращающую список идентификаторов вершин на пути от корня дерева к заданной вершине (например, идентификаторов всех вершин на пути от страницы «Главная» к странице «Архивная»).

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

Пример 47: формирование и анализ связанных структур

7.1.2. Пример 47: формирование и анализ связанных структур

Для решения задач из данного примера нам понадобится новая таблица, которую мы создадим в базе данных «Исследование». Эта таблица будет хранить граф (допустим, что это будет информация о стоимости доставки книг из одного города в другой для организации сотрудничества с другими библиотеками).

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

Поле cn_bidir в таблице connections является признаком того, что стоимость доставки одинакова как при отправке книги из города cn_from в город cn_to,

так и из cn_to в cn_from.

«column»

*PK ct_id: INT

* ct_name: VARCHAR(50)

«PK»

+ PK_cities(INT)

+PK_citie® \ 1

+ PK_cities/

(cn_from = ct_id)

(cn_to = ct_id)

+FK_connectiOnS_>cities1

«FK»

 

+ FK connections cities2

 

_J __________0'.

 

 

dm SQLServer2012

(cn_from = ct_id)

(cn_to = ct_id)

 

«FK»

«FK»

 

+FK_connections citieslFK connections cities2

 

_______ |0,.*

________I 0,,*~

 

connections

В

cities

«column»

*PK ct_id: NUMBER(10)

* ct_name: NVARCHAR2(50)

«PK»

+ PK_cities(NUMBER)

+PK_cities/\ 1

+PK_cities/\ 1

(cn_from = ct_id)

(cn_to = ct_id)

«FK»

«FK»

+FK_ connections citie+FK_connections_cities2

|0..* I 0..*

«column»

*pfK cn_from: INT *pfK cn_to: INT

cn_cost: DOUBLE

* cn_bidir: ENUM = ('N','Y')

«FK»

+FK_connections_cities1(INT)

+FK_connections_cities2(INT)

«PK»

+PK_connections(INT , INT)

«column»

*pfK cn_from : i nt *pfK cn_to: int

cn_cost: money * cn_bi di r: char(1)

«FK»

+FK_connections_cities1 (int)

+FK_connections_cities2(int)

«PK»

+PK_connecti ons(int, i nt)

« ch eck»

+CHK_bidir(char)

«column»

*pfK cn_from: NUMBER(10) *pfK cn_to: NUMBER(10)

cn_cost: NUMBER(15,4) * cn_bidir: CHAR(1)

«FK»

+FK_connecti ons_cities1(NUMBER)

+FK_connecti ons_cities2(NUMBER)

«PK»

+PK_connections(NUMBER, NUMBER)

«check»

+CHK_bidir(CHAR)

 

 

MySQL

MS SQL Server

Oracle

 

Рисунок 7.c — Таблицы cities и connections во всех трёх СУБД

Сохраним в таблице cities следующий набор данных.

 

 

 

 

 

 

ct_id

 

ct_name

 

 

1

 

Лондон

 

 

2

 

Париж

 

 

3

 

Мадрид

 

 

4

 

Токио

 

 

5

 

Москва

 

 

6

 

Киев

 

 

7

 

Минск

 

 

8

 

Рига

 

 

9

 

Варшава

 

 

10

 

Берлин

 

 

42 http://www.amazon.com/dp/1558609202/

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

Пример 47: формирование и анализ связанных структур

Сохраним в таблице connections следующий набор данных.

cn_from

cn_to

cn_cost

cn_bidir

1

5

10

Y

1

7

20

N

7

1

25

N

7

2

15

Y

2

6

50

N

6

8

40

Y

8

4

30

N

4

8

35

N

8

9

15

Y

9

1

20

N

7

3

5

N

3

6

5

N

Теперь, когда все данные подготовлены, мы можем переходить к задачам.

Задача 7.1.2.a{492}: доработать модель базы данных таким образом, чтобы для прямых маршрутов (без пересадок), цена перемещения по которым «туда» и «обратно» одинакова, в запросе на поиск такого маршрута можно было произвольно менять местами точки отправки и назначения.

Задача 7.1.2.b{493}: написать хранимую процедуру, проверяющую существование маршрута (с возможными пересадками) между двумя указанными городами, и вычисляющую стоимость отправки книги по такому маршруту (при его наличии).

Ожидаемый результат 7.1.2.a.

Запрос вида

1SELECT *

2FROM {источник данных}

3WHERE {откуда} = 5

4AND{куда} = 1

должен возвращать такой результат (обратите внимание: в представленных выше

cn_from

cn_to

cn_cost

cn_bidir

5

1

10

Y

данные нет маршрута из города 5 в город 1, есть только из 1 в 5, но этот маршрут

— двунаправленный):

Например, для городов с идентификаторами 1 и 6 хранимая процедура должна возвратить такие данные.

Ожидаемый результат 7.1.2.b.

cn_from

cn_to

cn_cost

cn_bidir

cn_steps

cn_route

1

6

31

N

3

1,7,3,6

1

6

85

N

3

1,7,2,6

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

Пример 47: формирование и анализ связанных структур

Решение 7.1.2.a{491}.

Для решения этой задачи нам необходимо обеспечить такое поведение СУБД, чтобы для двунаправленных маршрутов при указании условия поиска как cn_from=A ADN cn_to=B в выборку попадали также и маршруты, для которых

выполняется условие cn_from=B AND cn_to=A. Проще всего такого эффекта можно добиться с использованием представлений.

В строке 8 представленного ниже кода для MySQL можно было не писать ключевое слово DISTINCT (т.к. по умолчанию (без ключевого слова ALL) оператор UNION работает в DISTINCT-РЄЖИМЄ), но оно там есть для наглядности, чтобы подчеркнуть необходимость устранения дублирующихся записей.

MySQL I Решение 7.1.2.a |

1

CREATE

REPLACE VIEW 'connections bidir'

2

AS

 

3

SELECT

'cn from',

4

 

'cn to',

5

 

'cn cost'

6

 

'cn bidir'

7

FROM

'connections'

8UNION DISTINCT

9SELECT 'cn to',

10'cn from' ,

11'cn cost'

12'cn bidir'

13

FROM

'connections'

14

WHERE

'cn_bidir' = 'Y'

На примере решения для MySQL рассмотрим подробно, как работает такое представление. Если выбрать из него все данные, получится следующая картина. Серым фоном отмечены строки, появившиеся в результате выполнения UNION-

части запроса: для всех двунаправленных маршрутов добавились записи с инвертированными пунктами отправки и назначения.

cn_from

cn_to

cn_cost

cn_bidir

1

5

10

Y

1

7

20

N

2

6

50

N

3

6

6

N

4

8

35

N

6

8

40

Y

7

1

25

N

7

2

15

Y

7

3

5

N

8

4

30

N

8

9

15

Y

9

1

20

N

5

1

10

Y

8

6

40

Y

2

7

15

Y

9

8

15

Y

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

Пример 47: формирование и анализ связанных структур

Теперь выполнение запроса

MySQL Решение 7.1.2.a (проверка работоспособности)

1 SELECT *

2 FROM 'connections bidir'

3WHERE 'cn from' = 5

4AND 'cn to' = 1

вернёт корректный ожидаемый результат.

cn_from

cn_to

cn_cost

cn_bidir

5

1

10

Y

В MS SQL Server и Oracle синтаксис оператора UNOIN не допускает явного указания слова DISTINCT (строка 8 двух показанных ниже запросов), но это — не проблема, т.к. по умолчанию (без ключевого слова ALL) оператор UNION работает в DISTINCT-режиме.

MS SQL

 

Решение 7.1.2.a

 

 

 

CREATE VIEW [connections_bidir]

2

AS

 

 

 

 

 

3

 

SELECT [cn_from],

 

4

 

 

 

[cn_to],

 

5

 

 

 

[cn_cost],

 

6

 

 

 

[cn_bidir]

 

 

 

 

FROM

[connections]

 

8

 

UNION

 

 

 

9

 

SELECT [cn_to],

 

10

 

 

 

[cn_from],

 

11

 

 

 

[cn_cost],

 

12

 

 

 

[cn_bidir]

 

13

 

FROM

[connections]

 

14

 

WHERE

[cn_bidir] = 'Y'

 

 

 

 

Oracle

 

Решение 7.1.2.a

 

 

1

CREATE OR REPLACE VIEW

"connections_bidir"

2

AS

 

 

 

 

 

3

 

SELECT "cn_from",

 

4

 

 

 

"cn_to"

 

5

 

 

 

"cn_cost",

 

6

 

 

 

"cn_bidir"

 

 

 

 

FROM

"connections"

 

8

 

UNION

 

 

 

9

 

SELECT "cn_to"

 

10

 

 

 

"cn_from",

 

11

 

 

 

"cn_cost",

 

12

 

 

 

"cn_bidir"

 

13

 

FROM

"connections"

 

14

 

WHERE

"cn _bidir"=

Y'

 

 

 

 

 

 

 

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

Решение 7.1.2.b{491}.

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

Традиционно начнём с MySQL и рассмотрим два варианта решения, первый из которых максимально использует возможности СУБД, а второй эмулирует классическое алгоритмическое решение.

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

Пример 47: формирование и анализ связанных структур

Впервом варианте реализуется следующий подход43:

в оперативной памяти (ENGINE = HEAP) создаётся временная таблица для хранения найденных путей (строки 10-18);

в созданную таблицу переносятся все данные из таблицы connections с учётом двунаправленности некоторых связей (строки 21-40; аналогичный подзапрос, учитывающий двунаправленные связи, используется в строках 6880 — фактически, он представляет собой ничто иное, как тело представления из решения{492} задачи 7.1.2.a{491});

выполняется цикл поиска производных маршрутов (строки 45-92), в котором:

оусловием выхода является отсутствие новых маршрутов (функция

MySQL ROW_COUNT возвращает количество записей, затронутых последней операцией модификации данных);

оидея поиска новых маршрутов строится на том, чтобы к уже найденным маршрутам добавлять следующие шаги (конечная точка найденного маршрута совпадает с отправной точкой связи между городами, что проверяется в условии объединения в строке 81; условие в строках 8283 исключает порождение циклических маршрутов; условие в строках 88-89 исключает бесконечное повторное дублирующихся маршрутов между двумя любыми городами).

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

Уэтого решения есть два недостатка:

предварительное построение всех возможных маршрутов избыточно и приводит к бессмысленным затратам памяти и потере производительности;

альтернативные маршруты могут быть обнаружены только в том случае, если у них одинаковая длина (т.е. они найдены на одном шаге цикла).

MySQL Решение 7.1.2.b (код процедуры, первый вариант)

1DELIMITER $$

2CREATE PROCEDURE FIND PATH(IN start node INT,

3

IN finish node INT)

4BEGIN

5DECLARE rows_inserted INT DEFAULT 0;

7-- Пересоздание временной таблицы для хранения маршрутов

8-- (именно DROP/CREATE на случай, если такая таблица была):

9DROP TABLE IF EXISTS 'connections temp';

10CREATE TABLE IF NOT EXISTS 'connections temp'

11(

12'cn from' INT,

13'cn to' INT,

14'cn cost' DOUBLE,

15'cn bidir' CHAR 1 ,

16'cn steps' SMALLINT,

17'cn route' VARCHAR 1000)

18) ENGINE = MEMORY;

43 https://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html

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

Пример 47: формирование и анализ связанных структур

MySQL

і

Решение 7.1.2.b (код процедуры, первый вариант) (продолжение)

|

19-- Первичное наполнение временной таблицы

20-- существующими маршрутами:

21INSERT INTO 'connections temp'

22SELECT 'cn from',

23'cn to',

24'cn cost'

25'cn bidir',

261

27CONCAT('cn from', ',', 'cn to')

28

FROM

(SELECT 'cn from',

29

 

 

'cn to',

30

 

 

'cn

cost'

31

 

 

'cn

bidir'

32

 

FROM

'connections'

33UNION DISTINCT

34SELECT 'cn to',

35

 

'cn from' ,

36

 

'cn

cost'

37

 

'cn

bidir'

38

FROM

'connections'

39WHERE 'cn bidir' = 'Y'

40) AS 'connections bidir';

41

42— Наполнение временной таблицы производными

43— маршрутами:

44SET rows inserted = ROW COUNT();

45WHILE (rows inserted > 0)

46DO

47INSERT INTO 'connections temp'

48SELECT 'connections next' 'cn from',

49

'connections next' 'cn to'

 

50

'connections next' 'cn cost',

 

51

'connections next' 'cn bidir',

 

52

'connections next' 'cn steps',

 

53

'connections next' 'cn route'

 

54

FROM (SELECT 'connections temp' 'cn from' AS 'cn from',

55

'connections' 'cn to' AS 'cn to'

56

('connections temp' 'cn cost' +

 

57

'connections' 'cn cost') AS 'cn cost',

58

CASE

 

59

WHEN ('connections temp' 'cn bidir' = 'Y')

60

AND ('connections' 'cn bidir' =

'Y')

61

THEN 'Y'

 

62

ELSE 'N'

 

63

END AS 'cn bidir',

 

64

('connections temp' 'cn steps' + 1

AS 'cn steps',

65

CONCAT('connections temp' 'cn route', ',',

66

'connections' 'cn to') AS 'cn route'

67

FROM 'connections temp'

 

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

Пример 47: формирование и анализ связанных структур

MySQL I

Решение 7.1.2.b (код процедуры, первый вариант) (продолжение)

|

68

 

JOIN (SELECT 'cn from',

 

69

 

 

'cn to',

 

70

 

 

'cn cost',

 

71

 

 

'cn bidir'

 

72

 

FROM

'connections'

 

73

 

 

UNION DISTINCT

 

74

 

SELECT 'cn to',

 

75

 

 

'cn from' ,

 

76

 

 

'cn cost',

 

77

 

 

'cn bidir'

 

78

 

FROM

'connections'

 

79

 

WHERE

'cn bidir' = 'Y'

 

80

 

) AS 'connections'

 

81

 

ON 'connections temp' 'cn to' = 'connections' 'cn from'

82

 

AND FIND IN SET('connections' 'cn to',

 

83

 

 

'connections temp' 'cn route') = 0

84

 

) AS 'connections next'

 

85

 

LEFT JOIN 'connections temp'

 

86

 

ON 'connections next' 'cn from' = 'connections temp' 'cn from'

87

 

AND 'connections next' 'cn to' = 'connections temp' 'cn to'

88

 

WHERE 'connections temp' 'cn from' IS NULL

 

89

 

AND 'connections_temp' 'cn_to' IS NULL;

 

90

 

 

 

 

91

 

SET rows inserted = ROW COUNT();

 

92

 

END WHILE;

 

 

93

 

— Извлечение маршрутов, соответствующих условию поиска:

94

 

SELECT *

 

 

95

 

FROM 'connections temp'

 

96

 

WHERE 'cn from' = start node

 

97

 

AND 'cn to' = finish node

 

98

 

ORDER BY 'cn cost' ASC;

 

99

 

DROP TABLE IF EXISTS 'connections temp';

 

100

END;

 

 

101

$$

 

 

 

102

DELIMITER ;

 

 

 

 

 

 

 

Альтернативное решение, построенное на основе классического алгоритма «поиска вглубь», требует предварительной подготовки: создания в оперативной памяти (ENGINE = MEMORY) двух таблиц и установки максимального уровня вложенности рекурсивных вызовов.

MySQL

Решение 7.1.2.b (подготовка ко второму варианту решения)

|

1— Создание таблицы для хранения текущего пути:

2CREATE TABLE IF NOT EXISTS 'current_path'

3(

4'cp id' INT PRIMARY KEY AUTO INCREMENT

5'cp from' INT,

6'cp to' INT,

7'cp cost' DOUBLE,

8'cp bidir' CHAR 1

9) ENGINE = MEMORY;

10

11— Создание таблицы для хранения готовых путей:

12CREATE TABLE IF NOT EXISTS 'final_paths'

13(

14'fp id' DOUBLE,

15'fp from' INT,

16'fp to' INT,

17'fp cost' DOUBLE,

18'fp bidir' CHAR 1

19) ENGINE = MEMORY;

20

21— Установка максимального уровня вложенности рекурсивных вызовов:

22SET @@SESSION max sp recursion depth = 255

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

Пример 47: формирование и анализ связанных структур

Теперь можно реализовывать алгоритм:

если текущий путь пуст, отправной точкой является точка старта, иначе отправной точкой является точка прибытия последней связи в пути (строки 4054);

открыть курсор для выбора всех связей между городами (строки 18-32, 56);

для всех связей повторять цикл, в котором:

опроверить, совпадает ли отправная точка рассматриваемой связи с текущей отправной точкой (строки 69-72) и, если нет, перейти к следующей итерации цикла;

опроверить, не присутствует ли уже рассматриваемая связь в пути (строки 74-80) и не приводит ли переход по этой связи к циклическому маршруту (строки 83-88) — в случае выполнения любого из этих условий перейти к следующей итерации цикла;

опроверить (строка 91), не совпала ли конечная точка связи с точкой финиша:

если совпала — мы нашли путь, для которого генерируем уникальный идентификатор (строка 93) и переносим в таблицу для хранения найденных путей (строки 95-118), не забыв добавить в конец саму связь, которую мы только что рассматривали (строки

108-118);

если не совпала — путь ещё не найден, а потому: добавляем рассматриваемую связь к текущему пути (строки 121-131), выполняем рекурсивный вызов (строка 134), после которого убираем из текущего пути последнюю связь (строки 137-140: MySQL не позволяет одновременно читать и удалять данные из таблицы, потому идентификатор последней записи мы помещаем в переменную в строках 137-138, а затем используем в условии в строке 140).

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

MySQL

Решение 7.1.2.b (код процедуры, второй вариант) |

 

1

DELIMITER .....................

 

$$ .......

*

2

CREATE PROCEDURE

FIND_PATH

(IN

start_node INT,

3

 

IN finish node INT)

4BEGIN

5-- Признак выхода из цикла курсора: DECLARE done INT DEFAULT 0;

6

 

7

-- Переменные для извлечения данных из курсора: DECLARE DECLARE DECLARE

8DECLARE cn_from_value INT DEFAULT 0;

9-- cn_to_value INT DEFAULT 0;

10Текущая cn_cost_value DOUBLE DEFAULT 0;

11cn_bidir_value CHAR(1) DEFAULT 0;

12"отправная точка"

13-- ВАЖНО! Эту переменную нельзя делать @глобальной ! DECLARE from node INT

14 DEFAULT 0;

15

16

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

Пример 47: формирование и анализ связанных структур

MySQL

і

Решение 7.1.2.b (код процедуры, второй вариант) (продолжение)

|

17-- Курсор

18DECLARE nodes cursor CURSOR FOR

19SELECT *

20FROM (SELECT 'cn from',

21

 

'cn to',

22

 

'cn

cost'

23

 

'cn

bidir'

24

FROM

'connections'

25UNION DISTINCT

26SELECT 'cn to',

27

 

'cn from',

28

 

'cn

cost'

29

 

'cn

bidir'

30

FROM

'connections'

31

WHERE

'cn bidir' = 'Y')

32AS 'connections bidir';

33-- здесь можно дописать

34-- WHERE 'cn from' = from node

35-- и убрать далее

36-- IF (cn_from_value != from_node)

38DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1

39

40IF ((SELECT COUNT 1

41FROM 'current_path') = 0)

42THEN

43-- Если текущий путь пуст, отправной точкой

44-- является точка старта

45SET from node = start node

46ELSE

47-- Если текущий путь НЕ пуст, отправной точкой

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

49SET from node = (SELECT 'cp to'

50

FROM 'current_path'

51

WHERE 'cp id' = (SELECT MAX('cp id')

52

FROM 'current_path')

53

);

54

END IF;

55

 

56

OPEN nodes_cursor;

57

 

58nodes loop: LOOP

59FETCH nodes cursor INTO cn from value

60

cn to value

61

cn

cost value,

62

cn

bidir value;

63IF done THEN

64LEAVE nodes loop;

65END IF;

66

67— Отправная точка связи не совпадает с текущей

68— отправной точкой, пропускаем

69IF (cn from value != from node

70THEN

71ITERATE nodes loop;

72END IF;

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