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

Пример 45: управление транзакциями в триггерах, хранимых функциях и процедурах

Решение для Oracle выглядит следующим образом. Да, именно так и выглядит, т.к. в Oracle нет такого явления, как автоподтверждение транзакций — этот эффект может быть реализован некоторыми средствами работы с СУБД, но сама СУБД всегда ждёт явного COMMITT или ROLLBACK.

Oracle

Решение 6.2.3.b (код функции)

1CREATE FUNCTION NO AUTOCOMMITT

2RETURN INT

3DETERMINISTIC

4IS

5BEGIN

6DBMS OUTPUT.PUT LINE('Have a nice day :)');

7RETURN 1

8END;

Проверить работоспособность и корректность представленного решения можно выполнением следующего кода.

Oracle Решение 6.2.3.Ь (код для проверки работоспособности решения)

1 SET SERVEROUTPUT ON;

2 SELECT NO AUTOCOMMITT FROM DUAL;

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

ЧРешение 6.2.3.C 465.

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

В MySQL таким уровнем является READ UNCOMMITTED, в MS SQL Server —

тоже READ UNCOMMITTED или SNAPSHOT (но SNAPSHOT может приводить к допол-

нительным расходам ресурсов), в Oracle чтение данных всегда происходит в независимом режиме, потому в этой СУБД можно использовать READ COMMITTED (тем более, что READ UNCOMMITTED в Oracle нет).

Решение для MySQL выглядит следующим образом.

MySQL і

Решение 6.2.3.C (код процедуры)

[

1DELIMITER $$................. ........................

2CREATE PROCEDURE COUNT_ROWS(IN table_name VARCHAR 150),

3

OUT rows_in_table INT)

4

BEGIN

5

SET SESSION TRANSACTION

6

ISOLATION LEVEL READ UNCOMMITTED;

7

 

8

SET @count_query =

9

CONCAT('SELECT COUNT(1) INTO @rows_found

10

FROM ' , table_name);

11

 

12PREPARE count_stmt FROM @count_query

13EXECUTE count_stmt;

14DEALLOCATE PREPARE count_stmt

15

16SET rows_in_table := @rows_found

17END;

18$$

19DELIMITER ;

Проверить работоспособность и корректность представленного решения можно выполнением следующего кода.

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

Пример 45: управление транзакциями в триггерах, хранимых функциях и процедурах

MySQL Решение 6.2.3.С (код для проверки работоспособности решения)

1CALL COUNT_ROWS('subscriptions', @rows_in_table);

2SELECT @rows in table

Решение для MS SQL Server выглядит следующим образом.

MS SQL

Решение 6.2.3.С (код процедуры) і

1

CREATE PROCEDURE ....COUNTJROWS ...‘ ..........

2

@table_name NVARCHAR 150

 

3

@rows in table INT OUTPUT

 

4AS

5DECLARE @count_query NVARCHAR(1000)

6

7SET TRANSACTION ISOLATION

8LEVEL READ UNCOMMITTED;

9

10SET @count_query =

11CONCAT('SET @rows_f = (SELECT COUNT(1) FROM [', @table_name, '])');

12EXECUTE sp_executesql @ count_query

13

N'@rows_f INT OUT',

14

@rows in table OUTPUT;

15

GO

Проверить работоспособность и корректность представленного решения можно выполнением следующего кода.

MySQL і Решение 6.2.3.С (код для проверки работоспособности решения)

1DECLARE. @res INT ;

2EXECUTE COUNT_ROWS 'subscriptions', @res OUTPUT;

3SELECT @res;

Решение для Oracle выглядит следующим образом.

Oracle і Решение 6.2.3.С (код процедуры)

1

2

3

4

5

6

7

8

9

10

11

12

13

CREATE PROCEDURE COUNT_ROWS

table_name IN VARCHAR,

 

 

rows_in_table OUT NUMBER) AS count_query

VARCHAR 10001

:= '';

 

BEGIN

 

 

EXECUTE IMMEDIATE 'ALTER SESSION SET

 

ISOLATION_LEVEL = READ COMMITTED';

 

count_query := 'SELECT COUNT(1) FROM "'|| table_name ||

'"';

EXECUTE IMMEDIATE count_query INTO rows_in_table' END;

 

/

 

 

Проверить работоспособность и корректность представленного решения можно выполнением следующего кода.

Oracle і Решение 6.2.3.С (код для проверки работоспособности решения)

1DECLARE

2res NUMBER;

3BEGIN

4COUNT ROWS('subscriptions', res ;

5DBMS OUTPUT.PUT LINE('Rows: ' || res);

6END;

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

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

Пример 45: управление транзакциями в триггерах, хранимых функциях и процедурах

Задание 6.2.3.TSK.A: создать на таблице subscriptions триггер, определяющий уровень изолированности транзакции, в котором сейчас проходит операция обновления, и отменяющий операцию, если уровень изолированности транзакции отличен от REPEATABLE READ.

Задание 6.2.3.TSK.B: создать хранимую функцию, порождающую исключительную ситуацию в случае, если выполняются оба условия:

режим автоподтверждения транзакций выключен;

функция запущена из вложенной транзакции.

Подсказка: эта задача имеет решение только для MS SQL Server.

Задание 6.2.3.TSK.C: создать хранимую процедуру, выполняющую подсчёт количества записей в указанной таблице таким образом, чтобы она возвращала максимально корректные данные, даже если для достижения этого результата придётся пожертвовать производительностью.

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

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

Раздел 7: Решение типичных задач и выполнение типичных операций

7.1.Работа с иерархическими и связанными структурами

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

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

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

dm MySQL

dm SQLServer2012

dm Oracle

 

 

site_pages

«column»

*PK sp_id: INT FK sp_parent: INT

sp_name: VARCHAR(200)

«FK»

+FK_site_pages_site_pages(INT)

«PK»

+PK_site_pages(INT)

0..‡‡‡‡‡‡‡‡‡‡ §§§§§§§§§§

(sp_parent = sp_id)

«FK»

+PK_ate_pages

+FK_site_pages_9te_pages

«FK»

site_pages

«column»

*PK sp_id: int FK sp_parent: int

sp_name: nvarchar(200)

+FK_site_pages_site_pages(int)

«PK»

+PK_site_pages(i nt)

0..*

/\ 1

(sp_parent = sp_id)

«FK»

+ FK_site_pages_site_pages+PK_site_Pages

«FK»

+ FK_site_pages_site_pages(NUMBER)

«PK»

site_pages

+PK_site_pages(NUMBER)

0..*

/\ 1

(sp parent = sp id)

 

«FK»

.

+FK_site_pages_9te_pages +PK_site_Pages

 

MySQL

MS SQL Server

Oracle

 

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

Сохраним в таблице site_pages следующий набор данных, визуально

представленный на рисунке 7.b.

 

 

 

 

 

sp_id

sp_parent

sp_name

 

1

NULL

Главная

 

2

1

Читателям

 

3

1

Спонсорам

 

4

1

Рекламодателям

 

5

2

Новости

 

6

2

Статистика

 

7

3

Предложения

 

8

3

Истории успеха

 

9

4

Акции

 

10

1

Контакты

 

11

3

Документы

 

12

6

Текущая

 

13

6

Архивная

 

14

6

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

 

39 http://www.amazon.eom/dp/1558609202/

«column»

§§§§§§§§§§PK sp_id: NUMBER(10)

FK sp_parent: NUMBER(10) sp_name: NVARCHAR2(200)

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

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

Рисунок 7.b — Визуальное представление карты сайта

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

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

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

Задача 7.1.1.c{485}: написать функцию, возвращающую список идентификаторов вершин на пути от заданной вершины к корню дерева (например, идентификаторов всех вершин на пути от страницы «Архивная» к странице

«Главная»).

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

Для вершины с идентификатором 2 (страница «Читателям») функция должна возвратить следующие данные: shildren_of_2

5,6,12,13,14

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

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

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

Для вершины с идентификатором 2 (страница «Читателям») запрос должен возвратить следующие данные:

sp_id

sp_name

2

Читателям

5

Новости

6

Статистика

12

Текущая

13

Архивная

14

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

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

Для вершины с идентификатором 13 (страница «Архивная») запрос должен возвратить следующие данные: path

13

6 __

2 __

1

Также допускается вариант:

path

13,6,2,1

Решение 7.1.1.a{475}.

Решение данной задачи для MS SQL Server и Oracle можно очень легко построить на основе рекурсивных общих табличных выражений. В MySQL же общие табличные выражения не поддерживаются, равно как нет и иной возможности сделать «рекурсивный JOIN», потому для этой СУБД решение будет достаточно не-

тривиальным.

Сначала приведём готовый код функции и пример её использования.

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

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

MySQL

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

1DELIMITER $$

2CREATE FUNCTION GET_ALL_CHILDREN start_node INT)

3RETURNS TEXT

4BEGIN

5DECLARE result TEXT;

6SELECT GROUP_CONCAT('children_ids' SEPARATOR ',') INTO result

7

FROM

(

 

 

8

 

SELECT 'sp_id', @parent_values :=

9

 

 

(

 

10

 

 

SELECT GROUP_CONCAT('sp_id' SEPARATOR ',')

11

 

 

FROM

'site_pages'

12

 

 

WHERE FIND_IN_SET('sp_parent',

13

 

 

 

@parent_values) > 0

14

 

 

) AS 'children_ids'

15

 

FROM

'site_pages'

16

 

JOIN

(SELECT @parent_values := start_nodel

17

 

 

 

AS 'initialisation'

18

 

WHERE

'sp_id' IN

@parent_values

19) AS 'data';

20RETURN result 1

21END$$

22DELIMITER ;

Проверить работоспособность и корректность представленного решения можно выполнением следующего кода.

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

1-- Проверка работоспособности функции:

2SELECT GET_ALL_CHILDREN'2) AS 'shildren_of_2';

4-- Использование функции:

5SELECT 'sp_id', 'sp_name', GET_ALL_CHILDREN('sp_id') AS 'children'

6FROM 'site pages';

Второй запрос возвратит следующие данные (список всех страниц сайта библиотеки с указанием идентификаторов всех их подстраниц).

sp_id

sp_name

children

1

Главная

2,3,4,10,5,6,7,8,9,11,12,13,14

2

Читателям

5,6,12,13,14

3

Спонсорам

7,8,11

4

Рекламодателям

9

5

Новости

NULL

6

Статистика

12,13,14

7

Предложения

NULL

8

Истории успеха

NULL

9

Акции

NULL

10

Контакты

NULL

11

Документы

NULL

12

Текущая

NULL

13

Архивная

NULL

14

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

NULL

Теперь рассмотрим, как работает это решение.

Очевидно, главной частью представленной функции является запрос в строках 6-19. Перепишем его без функции.

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

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

 

 

Решение 7.1.1.a

:,

основана

1

 

 

на

 

 

SELECT GROUP CONCAT( 'children ids' SEPARATOR ',') AS 'children ids'

2

FROM

(

 

 

 

3

 

SELECT 'sp_id', @parent_values :=

4

 

 

(

 

 

5

 

 

 

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

6

 

 

 

FROM

'site_pages'

7

 

 

 

WHERE FIND_IN_SET('sp_parent',

8

 

 

 

 

@parent values > 0

9

 

 

) AS 'children ids'

10

 

FROM

'site_pages'

11

 

JOIN

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

12

 

 

 

 

AS 'initialisation'

13

 

WHERE

'sp id' IN

@parent values)

14) AS 'prepared data'

Втаком виде этот запрос вернёт данные, представленные в ожидаемом результате задачи (если значение {родительская_вершина} равно 2).

Рассмотрим решение по частям.

1)Код в строках 11-12 инициализирует значение переменной @parent_values идентификатором вершины, для которой строится список идентификаторов дочерних вершин. В дальнейшем здесь будет храниться список вершин, но в начале работы здесь помещается только одно значение.

2)Код в строках 5-8 формирует набор идентификаторов дочерних элементов вершин, идентификаторы которых перечислены в переменной @parent_values. Полученный результат используется как новое значение переменной @parent_values.

3)Условие в строке 13 ограничивает выборку только теми вершинами, дочерние элементы которых ищутся на текущем шаге.

4)Алгоритм завершает работу, когда значение переменной @parent_values становится равным NULL.

5)Благодаря GROUP_CONCAT в строке 1 весь результат работу представляется

в виде одного списка идентификаторов, в котором они перечислены через запятую. Такое представление позволяет применять функцию FIND_IN_SET

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

Если выполнить отдельно строки 3-13 данного запроса, то будет получен следующий результат (сам результат представлен на сером фоне, чтобы не путать его с пояснениями).

Шаг

sp_id

children_ids

Новое значение @parent_values

Начальное состояние, поиск детей вершины 2.

 

1

2

5,6

5,6

Теперь надо найти детей вершин 5 и 6 (строка с вершиной 6 «схлопывается» из-за GROUP_CON- CAT, т.е. в такой выборке мы теряем все значения sp_id, кроме самого первого, но в @parent_values благодаря тому же GROUP_CONCAT попадают дети всех sp_id, даже тех, чьи

значения мы «потеряли»).

2

5

12,13,14

12,13,14

Теперь надо найти детей вершин 12, 13 и 14 (строки с вершинами 13 и 14 «схлопываются» из-за GROUP_CONCAT, т.е. в такой выборке мы теряем все значения sp_id, кроме самого первого, но в @parent_values благодаря тому же GROUP_CONCAT попадают дети всех sp_id, даже тех, чьи значения мы «потеряли»).

3

12

NULL

NULL

Теперь надо найти детей вершин... NULL, т.е. никаких: конец алгоритма.

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

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

Переходим к MS SQL Server. Здесь решение будет совершенно иным, т.к., вопервых, данная СУБД не поддерживает часть синтаксиса, необходимого для эмуляции решения MySQL, а во-вторых, с использованием возможностей MS SQL Server эта задача решается проще (несмотря на то, что кода будет больше).

MS SQL

Решение 7.1.1

1 CREATE.a FUNCTION GET ALL CHILDREN @parent INT, @mode VARCHAR 50))

2RETURNS @all children TABLE

3(

4id VARCHAR(max)

5)

6AS

7BEGIN

8 IF @mode = 'TABLE')

9BEGIN

10WITH [tree] ([sp_id], [sp_parent])

11AS

12(

13SELECT [sp id]

14

 

[sp_parent]

15

FROM

[site_pages]

16

WHERE

[sp id] = @parent

17UNION ALL

18SELECT [inner] [sp id]

19

 

[inner] [sp_parent]

20

FROM

[site_pages] AS [inner]

21

 

JOIN [tree]

22

 

ON [inner] [sp_parent] = [tree] [sp_id]

23)

24INSERT @all children

25SELECT CAST [sp id] AS VARCHAR)

26 FROM [tree]

27WHERE [sp id] != @parent

28END

29ELSE

30BEGIN

31WITH [tree] ([sp_id] [sp_parent])

32AS

33(

34SELECT [sp id],

35

 

[sp_parent]

36

FROM

[site_pages]

37

WHERE

[sp id] = 2

38UNION ALL

39SELECT [inner] [sp id],

40

 

[inner] [sp_parent]

41

FROM

[site_pages] AS [inner]

42

 

JOIN [tree]

43

 

ON [inner] [sp_parent] = [tree] [sp_id]

44)

45INSERT @all children

46SELECT STUFF((SELECT ',' + CAST [sp id] AS VARCHAR)

47

FROM

[tree]

48

 

WHERE [sp id] != 2

49

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

50

1, 1,

'');

51END;

52RETURN

53END;

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

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

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

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

 

 

1

SELECT * FROM

GET_ALL_CHILDREN 2

'STRING')

2

SELECT * FROM GET ALL CHILDREN 2,

'TABLE');

 

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

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

Воснове решения лежит т.н. рекурсивное общее табличное выражение41. Рассмотрим его отдельно.

MS SQL

Решение 7.1.1 .a (рекурсивное общее табличное выражение)

 

1

WITH [tree] ([sp_id], [sp_parent])

2

AS

 

 

3

(

 

 

4

SELECT [sp_id],

5

 

[sp_parent]

6

FROM

[site_pages]

 

WHERE

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

8

UNION ALL

 

9

SELECT [inner] [sp_id]

10

 

[inner] [sp_parent]

11

FROM

[site_pages] AS [inner]

12

 

JOIN

[tree]

13

 

ON

[inner] [sp_parent] = [tree] [sp_id]

14

)

 

 

15

SELECT

[sp_id]

16

FROM

[tree]

17

WHERE

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

Рекурсивное общее табличное выражение должно содержать две части:

в первой части (строки 4-7) происходит выборка родительской записи (для которой мы будем искать дочерние);

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

Поскольку первая часть (до оператора UNION) является обязательной, а по

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

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

В строках 31-50 кода функции находится то же самое рекурсивное общее табличное выражение, но при выборке результатов его выполнения мы применяем приём, подробно описанный в решении{72} задачи 2.2.2.a{71} для эмуляции функции

41 https://msdn.microsoft.com/en-us/library/ms175972%28v=sql.110%29.aspx

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