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

Пример 38: выполнение динамических запросов с помощью хранимых процедур

ления решена, остаётся только создать хранимую процедуру по аналогии с реше-

ниями для MySQL и MS SQL Server.

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

Пример 38: выполнение динамических запросов с помощью хранимых процедур

1

2

3

CREATE OR REPLACE PROCEDURE SHOW_TABLE_OBJECTS(table_name IN VARCHAR2, final_rc OUT SYS_REFCURSOR)

IS

4 query_text VARCHAR2(1000);

5BEGIN

6query_text := '

7SELECT ''foreign_key'' AS "object_type",

8CONSTRAINT_NAME AS "object_name"

9FROM ALL_CONSTRAINTS

10WHERE OWNER = USER

11AND TABLE NAME = '' FP TABLE NAME PLACEHOLDER ''

12

AND CONSTRAINT_TYPE

= ''R''

13

UNION

 

14

SELECT ''trigger''

AS "object_type",

15TRIGGER_NAME AS "object_name"

16FROM ALL_TRIGGERS

17WHERE OWNER = USER

18AND TABLE_NAME = ''_FP_TABLE_NAME_PLACEHOLDER_''

19UNION

20

SELECT ''view''

AS "object_type",

21"VIEW_NAME" AS "object_name"

22FROM TABLE(ALL_VIEWS_VARCHAR2)

23WHERE "TEXT" LIKE ''%"_FP_TABLE_NAME_PLACEHOLDER_" %''';

25

query_text := REPLACE query_text '_FP_TABLE_NAME_PLACEHOLDER_',

26

table_name);

27

 

28OPEN final_rc FOR query_text

29END;

30/

Но одно неудобство остаётся: чтобы получить результат работы такой процедуры, придётся использовать достаточно нетривиальный код:

 

Oracl

і

Решение 5.2.1 .b (код для выполнения хранимой процедуры и получения результата её работы)

|

e

 

 

 

 

 

1

 

DECLARE

 

2

 

 

rc SYS REFCURSOR;

 

3

 

 

object type VARCHAR2I500 ;

 

4

 

 

object name VARCHAR2I500 ;

 

5

 

BEGIN

 

6

 

 

SHOW_TABLE_OBJECTS('subscriptions', rc);

 

7

 

 

 

 

8LOOP

9FETCH rc INTO object type, object name;

10EXIT WHEN rc NOTFOUND;

11DBMS OUTPUT.PUT LINE object type || ' | ' || object name);

12END LOOP;

13CLOSE rc;

14END;

Идаже с таким нетривиальным кодом мы получаем результат в виде текста,

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

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

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

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

Пример 38: выполнение динамических запросов с помощью хранимых процедур

Oracle і Решение 5.2.1 .b (альтернативное решение в виде одной хранимой функции)

1CREATE OR REPLACE TYPE ........................"Show^table^objects^row" AS

2........... OBJECT

3

(

 

 

 

 

"field_a" VARCHAR2(500),

 

4

 

"field_b" VARCHAR2(32767)

 

5

 

);

 

 

 

 

6

 

 

 

 

/

 

 

 

 

7

 

 

 

 

CREATE TYPE

"show_table_objects_table"

 

8

 

IS TABLE OF

"show_table_objects_row";

 

9

 

/

 

 

 

 

10

 

 

 

 

 

 

 

 

 

11

 

CREATE OR REPLACE FUNCTION SHOW_TABLE_OBJECTS_FNC(table_name

IN

 

 

12

VARCHAR2)

 

 

 

 

13

RETURN

 

 

"show_table_objects_table" PIPELINED

 

 

 

 

 

14

AS

 

 

 

 

 

 

 

 

 

15

TYPE

type_rc

IS REF CURSOR;

 

16

 

rc

type_rc

 

 

 

 

 

17field_a VARCHAR2(500);

18field_b VARCHAR2(32767);

19query_text VARCHAR2(1000);

20BEGIN

21query_text := '

22SELECT ''foreign_key''AS "object_type",

23CONSTRAINT_NAME AS "object_name"

24FROM ALL_CONSTRAINTS

25WHERE OWNER = USER

26AND TABLE_NAME = ''_FP_TABLE_NAME_PLACEHOLDER_''

27

AND

CONSTRAINT_TYPE = ''R'' '

28

UNION

 

 

 

 

 

29

SELECT ''trigger''

AS

"object_type",

30TRIGGER_NAME AS "object_name"

31FROM ALL_TRIGGERS

32WHERE OWNER = USER

33AND TABLE_NAME = ''_FP_TABLE_NAME_PLACEHOLDER_''';

34

query_text := REPLACE query_text

'_FP_TABLE_NAME_PLACEHOLDER_',

35

table_name ;

36

 

 

37

OPEN rc FOR query_text;

 

38

LOOP

 

 

 

39

FETCH rc INTO field_a, field_b;

 

 

 

40

EXIT WHEN rc%NOTFOUND;

 

41

PIPE ROW("show_table_objects_row" field_a, field_b );

 

42

END LOOP;

 

43

CLOSE rc;

 

44

 

45query_text := '

46SELECT VIEW_NAME,

47TEXT

48FROM ALL_VIEWS

49WHERE OWNER = USER';

51OPEN rc FOR query_text;

52LOOP

53FETCH rc INTO field_a, field_b;

54EXIT WHEN rc%NOTFOUND;

55IF (INSTR field_b, '"' || table_name || '"') > 0)

56THEN

57PIPE ROW "show_table_objects_row" 'view', field_a );

58END IF;

59END LOOP;

60CLOSE rc;

61

62 RETURN; END;

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

Пример 38: выполнение динамических запросов с помощью хранимых процедур

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

Первый запрос (строки 20-42) просто выбирает данные по внешним ключам и триггерам без каких-то особых сложностей и нюансов: в переменную field_a помещается строковая константа ('foreign_key', 'trigger'), в переменную field_b — имя соответствующего внешнего ключа или триггера. Затем эти переменные используются для инициализации полей объекта show_table_objects_row.

Во втором запросе (строки 44-59) мы с использованием тех же переменных и того же объекта, что и в первом запросе, делаем следующее:

сначала в переменные field_a и field_b помещаются имя и текст представления соответственно (строка 52);

затем значение переменной field_b используется для проверки того факта,

что текст представления содержит имя анализируемой таблицы (строка 54), и больше это значение переменной field_b нам не нужно и нигде не используется;

наконец (в строке 56) мы используем текстовую константу 'view' и имя представления, хранящееся в переменной field_b, для инициализации полей объекта show_table_objects_row.

Теперь мы можем получить необходимый нам результат в виде таблицы.

Oracle Решение 5.2.1 .b (код для выполнения хранимой функции и получения результата в виде таблицы)

1 SELECT * FROM TABLE(SHOW_TABLE_OBJECTS_FNC('subscriptions'));

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

& Задание 5.2.1.TSK.A: создать хранимую процедуру, обновляющую все поля типа DATE (если такие есть) всех записей указанной таблицы на значение

текущей даты.

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

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

Пример 39: оптимизация производительности с помощью хранимых процедур

5.2.2.Пример 39: оптимизация производительности с помощью хранимых процедур

ОЗадача 5.2.2.a{388}: создать хранимую процедуру, запускаемую по расписанию каждый час и обновляющую данные в агрегирующей таблице

books_statistics (см. задачу 3.1.2.a{215}).

О Задача 5.2.2.b{393}: создать хранимую процедуру, запускаемую по расписанию каждый день и оптимизирующую (дефрагментирующую, компакти-

фицирующую) все таблицы базы данных.

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

Вначале каждого часа запускается созданная хранимая процедура. Данные

втаблице books_statistics приводятся в актуальное состояние.

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

В начале каждых суток запускается созданная хранимая процедура. Все таблицы базы данных приводятся в оптимизированное состояние.

уЦ7

■ч. Решение 5.2.2.a{388}.

Основной запрос (строки 14-29), выполняющий обновление данных в таблице books_statistics, построен на основе решения{216} задачи 3.1.2.a{215}.

Однако для повышения надёжности мы будем проверять существование таблицы books_statistics, которую собираемся обновить. Эта проверка реализована в строках 5-13. Если искомая таблица не существует, мы завершаем работу хранимой процедуры, возвращая соответствующее сообщение об ошибке.

MySQL I

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

|

1DELIMITER $$

2CREATE PROCEDURE UPDATE BOOKS STATISTICS()

3BEGIN

4

 

5

IF (NOT EXISTS(SELECT *

6

FROM 'information schema' 'tables'

7

WHERE 'table schema' = DATABASE()

8

AND 'table name' = 'books statistics'))

9THEN

10SIGNAL SQLSTATE '45001'

11SET MESSAGE TEXT = 'The 'books statistics' table is missing. !

12MYSQL ERRNO = 1001;

13END IF;

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

Пример 39: оптимизация производительности с помощью хранимых процедур

MySQL I

Решение 5.2.2.a (код процедуры) (продолжение)

|

14

UPDATE

'books statistics'

 

15

JOIN

 

 

 

16

(SELECT IFNULL('total', 0) AS 'total',

 

17

 

IFNULL('given', 0) AS 'given',

 

18

 

IFNULL('total' - 'given', 0 AS 'rest'

19

FROM

(SELECT (SELECT SUM('b quantity')

 

20

 

FROM

'books')

AS 'total',

21

 

(SELECT COUNT('sb book')

 

22

 

FROM

'subscriptions'

 

23

 

WHERE

'sb is active' = 'Y') AS 'given')

24

 

AS 'prepared data'

 

25

) AS 'src'

 

 

26

SET

 

 

 

27

'books statistics' 'total' = 'src' 'total',

 

28

'books statistics' 'given' = 'src' 'given',

 

29

'books statistics' 'rest' = 'src' 'rest';

 

30

END;

 

 

 

31

$$

 

 

 

32

DELIMITER ;

 

 

 

 

 

 

 

Проверим работоспособность (предварительно можно выполнить отдельный запрос на обновление данных в таблице books_statistics и установить все значения в ноль).

MySQL

Решение 5.2.2.a (запуск и проверка работоспособности)

1

CALL UPDATE BOOKS STATISTICS;

Установка запуска полученной хранимой процедуры по расписанию выглядит следующим образом. Предварительно в строке 1 мы включаем планировщик задач MySQL (для того, чтобы он продолжал работать и после перезапуска MySQL, необходимо добавить строку event_scheduler = on в файл настроек MySQL my.ini).

MySQL

Решение 5.2.2.a (установка запуска по расписанию)

1SET GLOBAL event_scheduler = ON;

CREATE EVENT 'update_books_statistics_hourly'

4ON SCHEDULE

5EVERY 1 HOUR

6STARTS DATE(NOW()) + INTERVAL (HOUR(NOW())+1) HOUR + INTERVAL 1 MINUTE

7ON COMPLETION PRESERVE

8DO

9CALL UPDATE BOOKS STATISTICS;

Убедиться, что соответствующая задача добавлена в планировщик, можно выполнив следующий запрос:

MySQL Решение 5.2.2.a (просмотр расписания)

1 SELECT * FROM 'information schema'.'events'

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

Переходим к MS SQL Server. Логика работы хранимой процедуры здесь полностью эквивалентна решению для MySQL: мы проверяем наличие таблицы books_statistics в строках 3-10 (и завершаем работу хранимой процедуры, если таблицы нет), а в строках 12-30 выполняем запрос, обновляющий данные.

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

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

Пример 39: оптимизация производительности с помощью хранимых процедур

MS SQL I

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

|

1CREATE PROCEDURE UPDATE BOOKS STATISTICS

2AS

3IF (NOT EXISTS(SELECT *

4

FROM [information schema] [tables]

5

WHERE [table catalog] = DB NAME()

6

AND [table name] = 'books statistics'))

7BEGIN

8RAISERROR ('The [books statistics] table is missing.', 16, 1);

9RETURN;

10END;

11

12UPDATE [books statistics]

13SET

14[books statistics] [total] = [src] [total],

15[books statistics] [given] = [src] [given],

16[books statistics] [rest] = [src] [rest]

17FROM [books statistics]

18JOIN

19(SELECT ISNULL([total], 0 AS [total]

20ISNULL([given], 0 AS [given]

21ISNULL([total] - [given], 0) AS [rest]

22

FROM

(SELECT (SELECT SUM [b

quantity]

 

23

 

FROM

[books])

AS [total]

24

 

(SELECT COUNT([sb book])

 

25

 

FROM

[subscriptions]

 

26

 

WHERE

[sb is

active] = 'Y') AS [given]

27AS [prepared data]

28) AS [src]

29ON 11

30GO

Проверим работоспособность (предварительно можно выполнить отдельный запрос на обновление данных в таблице books_statistics и установить все значения в ноль).

MS SQL Решение 5.2.2.a (запуск и проверка работоспособности)

1 EXECUTE UPDATE_BOOKS_STATISTICS

Установка запуска полученной хранимой процедуры по расписанию в MS SQL Server не только выглядит куда более сложно, но и не даст эффекта, если вы используете Express Edition этой СУБД: в этой версии отсутствует компонент SQL Server Agent, который и отвечает за выполнение запланированных задач.

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

Создать задачу.

Создать шаг задачи, описывающий запуск нашей хранимой процедуры.

Создать расписание, в котором указать требуемую периодичность выполнения.

Прикрепить ранее созданную задачу к только что созданному расписанию.

Передать созданную задачу на обработку в SQL Server Agent.

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

Пример 39: оптимизация производительности с помощью хранимых процедур

MS

QL |

решение 5.2.2.a (установка запуска по расписанию)

І

 

1

USE msdb ; GO -- https://msdn.microsoft.com/en-

2

us/library/ms182079.aspx EXEC dbo sp_add_j ob

3@job_name = N'Hourly [books_statistics] update';

4GO

5-- https://msdn.microsoft.com/en-us/library/ms187358.aspx

6EXEC sp_add_j obstep

7@job_name = N'Hourly [books_statistics] update',

8@step_name = N'Execute UPDATE_BOOKS_STATISTICS stored procedure',

9@subsystem = N'TSQL',

10@command = N'EXECUTE UPDATE_BOOKS_STATISTICS',

11@database_name = N'library_ex_2015_mod'; GO

12-- https://msdn.microsoft.com/en-us/library/ms187320.aspx

13EXEC dbo sp_add_schedule

14@schedule_name = N'UpdateBooksStatistics',

15@freq_type = 4,

16@freq_interval = 4

17@freq_subday_type = 8,

18@freq_subday_interval = 1,

19@active_start_time = 000100 ;

20USE msdb ; GO -- https://msdn.microsoft.com/en-

21us/library/ms186766.aspx EXEC sp_attach_schedule

22@job_name = N'Hourly [books_statistics] update',

23@schedule_name = N'UpdateBooksStatistics'; GO

24-- https://msdn.microsoft.com/en-us/library/ms178625.aspx

25EXEC dbo sp_add_j observer

26@job_name = N'Hourly [books_statistics] update';

27GO

28

29

30

31

32

33

Убедиться, что соответствующая задача добавлена в планировщик, можно выполнив следующий запрос (он покажет список задач даже в MS SQL Server Express Edition):

MS SQL Решение 5.2.2.a (просмотр расписания)

1SELECT * FROM msdb dbo sysschedules

Вданном обсуждении24 представлен очень хороший готовый SQL-скрипт для получения в удобной форме информации обо всех запланированных в MS SQL Server задачах.

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

Переходим к Oracle. Логика работы хранимой процедуры здесь полностью эквивалентна решениям для MySQL и MS SQL Server: мы проверяем наличие таблицы books_statistics в строках 5-15 (и завершаем работу хранимой процедуры, если таблицы нет), а в строках 17-27 выполняем запрос, обновляющий данные.

24 http://www.sqlservercentral.com/Forums/Topic410557-116-1.aspx

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

Пример 39: оптимизация производительности с помощью хранимых процедур

Oracl

і

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

|

 

e

 

 

 

 

 

 

 

 

1

CREATE OR REPLACE PROCEDURE UPDATE BOOKS STATISTICS

2

AS

 

 

 

 

 

 

3

rows count NUMBER;

 

 

 

4

BEGIN

 

 

 

 

 

5

SELECT COUNT 1

INTO rows count

 

6

FROM ALL TABLES

 

 

 

 

7

WHERE OWNER = USER

 

 

 

8

AND TABLE_NAME = 'books_statistics';

 

9

 

 

 

 

 

 

 

10

IF (rows count = 0

 

 

 

11

 

THEN

 

 

 

 

 

12

 

RAISE APPLICATION ERROR(-20001

 

13

 

 

 

 

 

'The "books statistics" table is missing.');

14

 

RETURN;

 

 

 

 

 

15

 

END IF;

 

 

 

 

 

16

 

 

 

 

 

 

 

17

UPDATE "books statistics"

 

 

18

 

SET ("total"

"given", "rest") =

 

19

 

(SELECT NVL "total", 0) AS "total",

20

 

 

NVL "given", 0) AS "given",

21

 

 

NVL "total" - "given", 0

AS "rest"

22

 

FROM

(SELECT (SELECT SUM("b quantity"

23

 

 

 

FROM

"books"

AS "total",

24

 

 

 

(SELECT COUNT "sb book"

25

 

 

 

FROM

"subscriptions"

26

 

 

 

WHERE

"sb is active" = 'Y') AS "given"

27

 

 

FROM dual

"prepared data");

28

END;

 

 

 

 

 

29

/

 

 

 

 

 

 

Проверим работоспособность (предварительно можно выполнить отдельный запрос на обновление данных в таблице books_statistics и установить все значения в ноль).

Oracle

Решение 5.2.2.a (запуск и проверка работоспособности)

 

1 EXECUTE UPDATE_BOOKS_STATISTICS

Установка запуска полученной хранимой процедуры по расписанию выглядит следующим образом.

Oracle

Решение 5.2.2.a (установка запуска по расписанию)

1

BEGIN

 

2

DBMS SCHEDULER.CREATE JOB (

3

job_name

=> 'hourly update books statistics',

4

job_type

=> 'STORED PROCEDURE',

5

job_action

=> 'UPDATE BOOKS STATISTICS',

6

start_date

=> '01-APR-16 1.00.00 AM',

7

repeat_interval

=> 'FREQ=HOURLY;INTERVAL=1',

8

auto_drop

=> FALSE

9

enabled

=> TRUE);

10

END;

 

 

 

 

Убедиться, что соответствующая задача добавлена в планировщик, можно выполнив следующий запрос:

Oracle Решение 5.2.2.a (просмотр расписания)

1 SELECT * FROM ALL_SCHEDULER_JOBS WHERE OWNER=USER

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

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

Пример 39: оптимизация производительности с помощью хранимых процедур

Решение 5.2.2. b{388}.

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

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

Для MySQL будет достаточно выполнить OPTIMIZE для всех таблиц.

Для MS SQL Server достаточно выполнить REORGANIZE или REBUILD для

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

Для Oracle будет достаточно выполнить SHRINK SPACE COMPACT CASCADE для всех таблиц.

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

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

Традиционно начинаем с MySQL.

Решение 5.2.2.b

1DELIMITER $$

2CREATE PROCEDURE OPTIMIZE ALL TABLES()

3BEGIN

4DECLARE done INT DEFAULT 0;

5DECLARE tbl name VARCHAR 200) DEFAULT '';

6DECLARE all tables cursor CURSOR FOR

7SELECT 'table name'

8

FROM

'information schema' 'tables'

9

WHERE

'table schema' = DATABASE()

10

AND

'table type' = 'BASE TABLE';

11

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

12

 

 

13

OPEN all tables cursor

14

 

 

15

tables loop: LOOP

16

FETCH all tables cursor INTO tbl name

17

IF done THEN

18

LEAVE tables loop;

19

END IF;

 

20

 

 

21SET Stable opt query = CONCAT('OPTIMIZE TABLE '', tbl name, •'•);

22PREPARE table opt stmt FROM Stable opt query

23EXECUTE table opt stmt;

24DEALLOCATE PREPARE table opt stmt

25

26 END LOOP tables loop;

27

28CLOSE all tables cursor;

29END;

30$$

31

32 DELIMITER ;

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