Материал: Using_MySql,_MS_SQL_Server_and_Oracle(1)

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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

MS SQL

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

53WHILE (1 = 1)

54BEGIN

55EXECUTE sp_executesql @empty_key_query,

56

 

N'@empty_k_v INT

OUT',

57

 

@empty_key_value

OUTPUT;

58

 

 

 

59IF (@empty_key_value IS NULL)

60BREAK;

61

 

 

 

62

 

EXECUTE sp_executesql @max_key_query,

 

63

 

N'@max_k_v INT

OUT',

64

 

@max_key_value

OUTPUT;

65

 

 

 

66SET @update_key_query =

67CONCAT('UPDATE [', @table_name, '] SET [', @pk_name,

68

 

'] = ', @empty_key_value, ' WHERE [', @pk_name, '] = ',

69

 

@max_key_value);

70

 

 

71

 

PRINT(CONCAT('Point 3. @update_key_query = ', @update_key_query));

72

 

 

73

 

EXECUTE sp_executesql @update_key_query;

74

 

 

75

 

SET @keys_changed = @keys_changed + 1;

76

 

 

77END;

78GO

SQL-запросы для определения первого свободного значения первичного ключа, максимального значения первичного ключа и обновления значения первичного ключа аналогичны решению для MySQL.

Небольшое отличие состоит в том, как получить в переменную результат выполнения динамического запроса: вместо SELECT ... INTO ... используется SET … = (SELECT ...), а при выполнении динамического SQL с помощью sp_executesql передаются дополнительные параметры, позволяющие поместить результат выполнения запроса в указанную переменную (строки 55-57, 62-64).

Поскольку MS SQL Server не поддерживает do … while циклы, мы вынуждены использовать бесконечный WHILE-цикл (строки 53-77), внутри которого будем проверять условие выхода и (при его выполнении) принудительно завершать цикл (строки 59-60).

Последнее отличие от MySQL состоит в способе вывода отладочной информации: в MS SQL Server мы можем использовать конструкцию PRINT (строки 26-27, 51-52, 71).

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

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

1DECLARE @res INT;

2EXECUTE COMPACT_KEYS 'subscriptions', 'sb_id', @res OUTPUT;

3SELECT @res;

4GO

5

6DECLARE @res INT;

7EXECUTE COMPACT_KEYS 'books', 'b_quantity', @res OUTPUT;

8SELECT @res;

9GO

10

11 SELECT * FROM [books] ORDER BY [b_quantity];

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

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

В первом случае мы получим сообщение об ошибке с просьбой убрать IDEN- TITY-свойство с поля sb_id таблицы subscriptions, во втором случае процедура выполнится (да, устранение свободных значений в поле, хранящем количество книг, лишено всякого здравого смысла, но для проверки работоспособности хранимой процедуры такой вариант годится).

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

Переходим к Oracle. Характерных для MS SQL Server проблем здесь нет (даже нет необходимости отключать триггеры, обеспечивающие автоинкрементацию первичного ключа при вставке, т.к. там именно INSERT-триггеры, а мы будем выполнять UPDATE).

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

1

 

CREATE PROCEDURE COMPACT_KEYS (table_name IN VARCHAR, pk_name IN VARCHAR,

2

 

keys_changed OUT NUMBER) AS

 

 

 

3empty_key_query VARCHAR(1000) := '';

4max_key_query VARCHAR(1000) := '';

5empty_key_value NUMBER := NULL;

6max_key_value NUMBER := NULL;

7update_key_query VARCHAR(1000) := '';

8BEGIN

9keys_changed := 0;

10

11DBMS_OUTPUT.PUT_LINE('Point 1. table_name = ' || table_name ||

12' || pk_name = ' || pk_name || ', keys_changed = ' || keys_changed);

13

14empty_key_query :=

15'SELECT MIN("empty_key") AS "empty_key"

16

 

FROM (SELECT "left"."' || pk_name || '" +

1 AS "empty_key"

17

 

FROM "' || table_name ||

'" "left"

 

 

 

18

 

LEFT OUTER JOIN

"' ||

table_name

||

'" "right"

19

 

ON

"left"."' ||

pk_name ||

20

 

'"

+ 1

=

"right"."'

||

pk_name || '"

21WHERE "right"."' || pk_name || '" IS NULL

22UNION

23SELECT 1 AS "empty_key"

24

 

FROM

"' || table_name

||

'"

 

25

 

WHERE

NOT EXISTS(SELECT "'

|| pk_name ||

'"

26

 

 

FROM

"'

|| table_name

|| '"

27

 

 

WHERE

"'

|| pk_name ||

 

28

 

 

'" = 1))

"prepared_data"

 

29

 

WHERE "empty_key" < (SELECT MAX("'

|| pk_name ||

'")

30

 

 

FROM "'

||

table_name || '")';

31

 

 

 

 

 

 

 

32max_key_query :=

33'SELECT MAX("' || pk_name || '") FROM "' || table_name || '"';

34

35DBMS_OUTPUT.PUT_LINE('Point 2. empty_key_query = ' || empty_key_query ||

36CHR(13) || CHR(10) || ' max_key_query = ' || max_key_query);

37

38LOOP

39EXECUTE IMMEDIATE empty_key_query INTO empty_key_value;

40EXIT WHEN empty_key_value IS NULL;

41EXECUTE IMMEDIATE max_key_query INTO max_key_value;

42update_key_query :=

43'UPDATE "' || table_name || '" SET "' || pk_name ||

44'" = ' || TO_CHAR(empty_key_value) || ' WHERE "' || pk_name ||

45'" = ' || TO_CHAR(max_key_value);

46DBMS_OUTPUT.PUT_LINE('Point 3. update_key_query = ' || update_key_query);

47EXECUTE IMMEDIATE update_key_query;

48keys_changed := keys_changed + 1;

49END LOOP;

50END;

51/

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

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

Получается, что единственные два отличия решения для Oracle от решения для MySQL состоят в способе вывода отладочной информации (строки 11-12, 3536, 46) и синтаксисе описания логики выхода из цикла (строка 40).

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

Oracle

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

1DECLARE

2keys_changed_in_table NUMBER;

3BEGIN

4COMPACT_KEYS('books', 'b_id', keys_changed_in_table);

5DBMS_OUTPUT.PUT_LINE('Keys changed: ' || keys_changed_in_table);

6

7COMPACT_KEYS('subscriptions', 'sb_id', keys_changed_in_table);

8DBMS_OUTPUT.PUT_LINE('Keys changed: ' || keys_changed_in_table);

9END;

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

Решение 5.2.1.b{375}.

В решении{375} задачи 5.2.1.a{375} мы уже рассмотрели логику формирования и выполнения динамических SQL-запросов в хранимых процедурах. Отличие решения этой задачи будет в том, что результатом работы хранимой процедуры будет не изменение в БД и возвращение числа правок, а возвращение таблицы.

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

MySQL Решение 5.2.1.b (код процедуры)

1DELIMITER $$

2CREATE PROCEDURE SHOW_TABLE_OBJECTS (IN table_name VARCHAR(150))

3BEGIN

4SET @query_text = '

5

 

SELECT

\'foreign_key\' AS

`object_type`,

6

 

 

`constraint_name` AS

`object_name`

7

 

FROM

`information_schema`.`table_constraints`

8

 

WHERE

`table_schema` = DATABASE()

9AND `table_name` = \'_FP_TABLE_NAME_PLACEHOLDER_\'

10AND `constraint_type` = \'FOREIGN KEY\'

11UNION

12

 

SELECT

\'trigger\'

AS

`object_type`,

13

 

 

`trigger_name`

AS

`object_name`

14

 

FROM

`information_schema`.`triggers`

15WHERE `event_object_schema` = DATABASE()

16AND `event_object_table` = \'_FP_TABLE_NAME_PLACEHOLDER_\'

17UNION

18

 

SELECT

\'view\'

AS

`object_type`,

19

 

 

`table_name`

AS

`object_name`

 

 

 

 

20

 

FROM

`information_schema`.`views`

21WHERE `table_schema` = DATABASE()

22AND `view_definition` LIKE \'%`_FP_TABLE_NAME_PLACEHOLDER_`%\'';

23SET @query_text = REPLACE(@query_text,

24

 

'_FP_TABLE_NAME_PLACEHOLDER_', table_name);

25

 

 

 

 

 

26PREPARE query_stmt FROM @query_text;

27EXECUTE query_stmt;

28DEALLOCATE PREPARE query_stmt;

29END;

30$$

31DELIMITER ;

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

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

Здесь (для разнообразия) текст итогового запроса мы получаем не с использованием функции CONCAT, а путём замены плейсхолдера _FP_TABLE_NAME_PLACEHOLDER_ на реальное имя таблицы в заранее подготовленном полном тексте запроса.

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

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

MySQL

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

1 CALL SHOW_TABLE_OBJECTS('subscriptions')

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

Переходим к MS SQL Server. Здесь логика решения полностью совпадает с решением для MySQL, за исключением того факта, что информацию о триггерах приходится извлекать из [sys].[triggers] как аналога

`information_schema`.`triggers`.

MS SQL Решение 5.2.1.b (код процедуры)

1

 

CREATE PROCEDURE SHOW_TABLE_OBJECTS

2

 

@table_name NVARCHAR(150)

3WITH EXECUTE AS OWNER

4AS

5DECLARE @query_text NVARCHAR(1000) = '';

6SET @query_text =

7

 

'SELECT

''foreign_key''

AS

[object_type],

8

 

 

[constraint_name]

AS

[object_name]

9

 

FROM

[information_schema].[table_constraints]

 

 

 

 

 

 

10WHERE [table_catalog] = DB_NAME()

11AND [table_name] = ''_FP_TABLE_NAME_PLACEHOLDER_''

12AND [constraint_type] = ''FOREIGN KEY''

13UNION

14SELECT ''trigger'' AS [object_type],

15

 

[name]

AS [object_name]

16

 

FROM [sys].[triggers]

17WHERE OBJECT_NAME([parent_id]) = ''_FP_TABLE_NAME_PLACEHOLDER_''

18UNION

19

 

SELECT

''view''

AS

[object_type],

20

 

 

[table_name] AS

[object_name]

21

 

FROM

[information_schema].[views]

22WHERE [table_catalog] = DB_NAME()

23AND [view_definition] LIKE ''%[_FP_TABLE_NAME_PLACEHOLDER_]%''';

24

25SET @query_text = REPLACE(@query_text, '_FP_TABLE_NAME_PLACEHOLDER_',

26

 

@table_name);

27

 

 

28EXECUTE sp_executesql @query_text;

29GO

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

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

1 EXECUTE SHOW_TABLE_OBJECTS 'subscriptions';

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

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

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

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

Текст представления (в котором мы ищем упоминание интересующей нас таблицы) хранится в поле типа LONG (это — не «длинное целое», это устаревший, но всё ещё используемый иногда текстовый тип данных22), и данные этого типа невозможно ни использовать в выражениях типа LIKE, ни преобразовать простым способом к другому типу (например, VARCHAR2).

Единственный более-менее адекватный способ извлечения LONG-данных с конвертацией к VARCHAR2 — создание хранимой функции. Существует универсальное решение23, но для простоты мы реализуем вариант, привязанный к конкретному источнику данных.

В представленном ниже коде мы создаём хранимую функцию, возвращающую таблицу (принцип создания и использования таких функций рассмотрен в решении{355} задачи 5.1.1.b{352}). Ключевая идея здесь состоит в том, что поле TEXT объекта all_views_row объявлено как VARCHAR2, и именно такой тип данных будет в выходной таблице. А с VARCHAR2-данными уже можно выполнять операции сравнения.

Вданном конкретном случае функция сначала готовит полный набор данных,

изатем возвращает его в виде сформированной таблицы, а PIPLINED-решение

будет представлено далее.

Oracle Решение 5.2.1.b (код функции для преобразования LONG в VARCHAR2)

1CREATE OR REPLACE TYPE "all_views_row" AS OBJECT

2(

3"VIEW_NAME" VARCHAR2(500),

4 "TEXT" VARCHAR2(32767)

5);

6/

7CREATE TYPE "all_views_table" IS TABLE OF "all_views_row";

8/

9

10CREATE OR REPLACE FUNCTION ALL_VIEWS_VARCHAR2

11RETURN "all_views_table"

12AS

13result_table "all_views_table" := "all_views_table"();

14CURSOR all_views_table_cursor IS

15SELECT VIEW_NAME,

16TEXT

17 FROM ALL_VIEWS

18WHERE OWNER = USER;

19BEGIN

20FOR one_row IN all_views_table_cursor

21LOOP

22result_table.extend;

23result_table(result_table.last) :=

24

"all_views_row"(one_row."VIEW_NAME", one_row."TEXT");

25END LOOP;

26RETURN result_table;

27END;

28/

Теперь, когда проблема с применением выражения LIKE к тексту представ-

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

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

22https://docs.oracle.com/cd/E11882_01/appdev.112/e25519/datatypes.htm#LNPLS346

23https://asktom.oracle.com/pls/apex/f?p=100:11:0::NO::P11_QUESTION_ID:839298816582

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

Источник: https://studfile.net/preview/16418462/