Бази даних-20210115T104840Z-001 / Реферат на тему _Современные СУБД_ / Using_MySql,_MS_SQL_Server_and_Oracle
.pdfПример 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: формирование и анализ связанных структур
Сохраним в таблице 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