Для меня PL/SQL есть логичное продолжение изучения SQL, но по нему много и статей, и курсов, причем даже на русском языке. С другой стороны, PL/SQL раскрывается достаточно редко, особенно среди бесплатных ресурсов, так что… Начинаем:)
Блоки процедур
В языке программирования PL/SQL существует несколько блоков, а именно: DECLARE, BEGIN и EXCEPTION. В этой статье мы разберем, как правильно объявлять и использовать переменные в каждом из этих блоков.
Блок DECLARE
В блоке DECLARE происходит объявление переменных, которые будут использоваться в теле процедуры. Пример объявления переменных:
DECLARE var_1 NUMERIC; var_2 VARCHAR2(64); var_3 DATE;
Кроме того, в PL/SQL можно использовать якорные типы данных. Вместо того чтобы вручную задавать тип данных переменной, можно привязать её к типу данных конкретного столбца в таблице. Это позволит избежать ошибок при изменении типа данных столбца в будущем.
DECLARE var_1 table_name.column_name%TYPE; var_2 table_name2.column_name2%TYPE;
Это полезно, если изменится тип данных столбца в таблице. Или, что чаще, удобнее прописать TYPE, чем смотреть в модель данных и искать, какое же указан тип данных для необходимых полей.
%ROWTYPE
Атрибут %ROWTYPE автоматически наследует структуру всех столбцов таблицы или курсора (не будет рассматриваться в этой статье), обеспечивая удобство работы с несколькими значениями одновременно. Кстати, сие называется “Record” (запись).
При обращении к полям записи используется тот же принцип, что и при обращении к таблицам внутри схем (scheme.table_name): RECORD.field_n.
______________________________________________________________________________
Рассмотрим на примере:
О пакете DBMS_OUTPUT
Хотя сначала стоит сказать, что же такое пакет.
Пакет – объединение нескольких процедур/функций в одно целое;
Обычно пакеты состоят из двух компонентов: спецификации (интерфейс) и тела (реализации интерфейса).
Спецификация пакета – список доступных процедур и функций (вместе со всеми переменными, определениями типов и курсорами).
Спецификация пакета содержит информацию о пакете, кроме того, в ней перечисляются все имеющиеся в пакете процедуры и функции (о разнице поговорим в другой раз). Обычно спецификация не содержит кода, код помещается в теле пакета. Процедуры и функции, перечисленные в спецификации доступны для просмотра, а реализация скрыта.
Итак, DBMS_OUTPUT…
Начнем с названия:
-
DBMS – Database Management System (оно же СУБД);
-
OUTPUT – в переводе на русский «вывод, результат».
Пакет чаще всего используется для отладки кода (дебаггинга) или для отображения некоторых сообщений и отчетов.
Наиболее часто используемые процедуры: PUT и PUT_LINE.
DBMS_OUTPUT.put('string') -- добавляет строку в буфер (без перевода строки) DBMS_OUTPUT.put_line('string') -- добавляет строку в буфер (с переводом строки)
Для показа данных сообщений иногда требуется прописать строки:
-- Перед кодом SET serveroutput ON -- Внутри блока BEGIN DBMS_OUTPUT.enable() -- обратная команда: DBMS_OUTPUT.disable()
В некоторых IDE по умолчанию не выводятся сообщения из DBMS_OUTPUT (SQL Developer, например. А вот PL/SQL Developer уже выводит и без просьб:), потому нужна строка с serveroutput.
С другой стороны, буфер с выводом данных может быть в целом неактивен, тогда пишем DBMS_OUTPUT.enable(n), где n есть число байт, которое будет хранить буфер.
Изложена лишь малая часть, которая пригодится в рамках текущей статьи. Если заинтересовались и хотите погрузиться глубже – добро пожаловать в документацию, которая, к слову, хорошо написана!)
DECLARE employee_record employees%ROWTYPE; BEGIN SELECT * INTO employee_record FROM employees WHERE employee_id = 100; DBMS_OUTPUT.PUT_LINE('ID: ' || employee_record.employee_id || ', Name: ' || employee_record.first_name || ' ' || employee_record.last_name); END;
Следует учитывать, что либо записывается все (*) в Record, либо записываются конкретные столбцы в указанные явно Fields:
– – – – – – – – – – – – – – ERROR – – – – – – – – – – – – – – – DECLARE employee_record employees%ROWTYPE; BEGIN SELECT employee_id INTO employee_record FROM employees WHERE employee_id = 100; DBMS_OUTPUT.PUT_LINE('ID: ' || employee_record.employee_id); END; – – – – – – – – – – – – – – ERROR – – – – – – – – – – – – – – –
– – – – – – – – – – – – – – RIGHT – – – – – – – – – – – – – – – DECLARE employee_record employees%ROWTYPE; BEGIN SELECT employee_id INTO employee_record.employee_id FROM employees WHERE employee_id = 100; DBMS_OUTPUT.PUT_LINE('ID: ' || employee_record.employee_id); END; – – – – – – – – – – – – – – RIGHT – – – – – – – – – – – – – – –
Удобства:
-
Динамичность (при изменении типа данных атрибутов %ROWTYPE автоматически это перенимает);
-
Лаконичность (не 10 переменных для 10 атрибутов, а 1).
Ограничения:
-
Предполагается использовать малую часть атрибутов таблицы – %ROWTYPE будет использовать больше памяти, чем необходимо;
-
Таблица будет часто обновляться новыми столбцами – %ROWTYPE их включит, а использоваться они, вероятно, не будут.
Как и с отдельной переменной, можно задавать поля в записи вручную, тем самым указывая их количество, тип данных. В таком случае синтаксис следующий:
TYPE type_rec_name IS RECORD (field_1 datatype, field_2 datatype, ... field_n datatype); var_rec type_rec_name;
Пример:
DECLARE TYPE DeptRecTyp IS RECORD ( dept_id NUMBER(4) NOT NULL := 10, dept_name VARCHAR2(30) NOT NULL := 'Administration', mgr_id NUMBER(6) := 200, loc_id NUMBER(4) := 1700 ); dept_rec DeptRecTyp; BEGIN DBMS_OUTPUT.PUT_LINE('dept_id: ' || dept_rec.dept_id); DBMS_OUTPUT.PUT_LINE('dept_name: ' || dept_rec.dept_name); DBMS_OUTPUT.PUT_LINE('mgr_id: ' || dept_rec.mgr_id); DBMS_OUTPUT.PUT_LINE('loc_id: ' || dept_rec.loc_id); END;
Пример взят отсюда. Там же можно найти иные примеры, а также некоторую дополнительную информацию.
______________________________________________________________________________
Блок основного кода (BEGIN)
В блоке BEGIN размещается основной код, который будет выполняться. Это может быть один или несколько SQL-запросов:
BEGIN -- Здесь размещается основной код END;
Рассматриваться подробнее будет в примерах ниже и в других статьях. Кратко: здесь могут применяться все те же SQL запросы, за исключением некоторых оговорок. Например, необходимо куда-то вкладывать выдаваемое запросом значение.
______________________________________________________________________________
Блок обработки ошибок (EXCEPTION)
В случае возникновения ошибок в блоке BEGIN, управление передается в блок EXCEPTION, где можно обработать исключения.
BEGIN -- Основной код EXCEPTION WHEN others THEN -- Обработка ошибок END;
Когда возникает ошибка, выполнение основного блока прерывается, и управление передаётся в блок EXCEPTION. Это позволяет предотвратить некорректное завершение программы и обработать ситуацию более осмысленно.
Стандартные исключения
PL/SQL имеет встроенные обработчики для стандартных ошибок:
-
NO_DATA_FOUND: Возникает, если SQL-запрос не возвращает строк, а результат ожидается через SELECT INTO.
-
TOO_MANY_ROWS: Выбрасывается, если SELECT INTO возвращает больше одной строки.
-
ZERO_DIVIDE: Ошибка деления на ноль.
-
INVALID_CURSOR: Использование неинициализированного или закрытого курсора.
-
VALUE_ERROR: Проблема с диапазоном или типом данных.
-
INVALID_NUMBER: Преобразование строки в число завершилось ошибкой.
______________________________________________________________________________
Пример обработки ошибки:
DECLARE tmp_var NUMBER; BEGIN SELECT 100 / 0 INTO tmp_var FROM dual; EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('SQLCODE = ' || SQLCODE); DBMS_OUTPUT.PUT_LINE('SQLERRM = ' || SQLERRM); END;
-
SQLCODE возвращает числовой код ошибки.
-
SQLERRM возвращает текстовое описание ошибки.
______________________________________________________________________________
Пользовательские исключения
Можно определять свои исключения и выбрасывать их с помощью RAISE:
DECLARE e_custom_exception EXCEPTION; num NUMBER := 5; BEGIN IF num < 10 THEN RAISE e_custom_exception; END IF; EXCEPTION WHEN e_custom_exception THEN DBMS_OUTPUT.PUT_LINE('Произошла ошибка: значение меньше 10!'); END;
______________________________________________________________________________
Универсальный обработчик WHEN OTHERS
Обработчик WHEN OTHERS перехватывает все исключения, которые не были обработаны целенаправленно (примеры выше):
EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Произошла ошибка: ' || SQLERRM); END;
Рекомендация: использовать WHEN OTHERS только с логированием ошибок для анализа и избегать скрытия критических проблем.
______________________________________________________________________________
Переменные в PL/SQL
Переменные в PL/SQL создаются и функционируют внутри определенного блока. Для того чтобы присвоить значение переменной, можно использовать два способа:
-
Присвоение значения «в лоб»:
DECLARE some_var VARCHAR2(32); BEGIN some_var := 'some_text'; END;
-
Получение значения через SQL-запрос:
DECLARE some_var1 VARCHAR2(32); some_var2 VARCHAR2(32); BEGIN SELECT some_col1, some_col2 INTO some_var1, some_var2 FROM some_tab WHERE some_col3 = some_filter; END;
В этом примере значения из столбцов some_col1 и some_col2 будут присвоены переменным some_var1 и some_var2 соответственно.
Константы в PL/SQL
Для создания константы, значение которой не будет изменяться в процессе выполнения программы, в блоке DECLARE следует использовать ключевое слово CONSTANT:
DECLARE var_name CONSTANT VARCHAR2(32) := 'Some constant value'; BEGIN DBMS_OUTPUT.PUT_LINE(var_name); END;
Общий синтаксис для объявления переменных в PL/SQL следующий:
variable_name [CONSTANT] data_type [NOT NULL] [:= | DEFAULT initial_value]
-
CONSTANT гарантирует, что значение переменной не изменится.
-
NOT NULL позволяет устанавливать ограничение на переменные, что важно, если при присвоении значения переменной может быть получен NULL.
-
DEFAULT используется для задания начального значения переменной.
То есть константа есть частный случай переменной.
______________________________________________________________________________
Подстановочные переменные (bind/host переменные)
Подстановочные переменные (или bind-переменные, VAR) инициализируются (получают значение) в блоке PL/SQL (либо ручной ввод с клавиатуры), но могут быть созданы и вне PL/SQL. Они могут быть использованы для передачи значений в SQL-запросы.
DECLARE var_name VARCHAR2(32); var_name2 VARCHAR2(32); tmp NUMBER; BEGIN :var_name := 'Some value'; SELECT id INTO tmp FROM tablename WHERE col_name = :var_name; -- Попробуйте и :var_name2, наглядно увидите разницу END;
Для вывода значений этих переменных можно использовать три метода:
-
DBMS_OUTPUT.PUT_LINE();
-
PRINT var_name;
-
SET autoprint ON; — автоматический вывод значения переменной при вызове.
А также:
-
VAR[IABLE] без создания переменной покажет список всех bind-переменных с их типами данных;
-
PRINT без имени переменной покажет список наименований переменных вместе с их значениями.
Скрин из IDE Присваивать значение переменной не обязательно – Oracle, если не найдет что подставить вместо :var_name, выведет окошко с предложением ввести подстановочный текст.
Также важно помнить, что подстановочные переменные не могут быть использованы для имен объектов базы данных (таких как таблицы, поля и представления), так как они подставляются на этапе подготовки (parse) запроса.
_____________________________________________________________________________
Замещающие переменные
Замещающие переменные — это переменные, которые объявляются и инициализируются символьным значением. Все переменные с префиксом & будут замещены значением на этапе подготовки SQL-запроса в клиенте.
Пример использования замещающей переменной:
DEFINE var_name = 'value'; SELECT * FROM table_ WHERE column_ = '&var_name';
Особенности замещающих переменных:
-
DEFINE — выводит значение и тип данных переменной.
-
Можно объявлять и использовать вне PL/SQL;
-
Присваивать значение переменной не обязательно, если оно не найдено, Oracle предложит ввести значение вручную.
Из IDE -
Можно создавать неограниченное количество таких переменных.
DEFINE var_name = 'value'; DEFINE var_name2 = 'value2'; DEFINE var_name3 = 'value3'; ...
______________________________________________________________________________
Сравнение типов переменных
Вот основные различия между переменными в PL/SQL:
|
Тип переменной |
Область видимости |
Типы данных |
Использование |
|
DECLARE |
Локальная для блока PL/SQL |
Любые типы данных |
Внутри блоков PL/SQL для временных вычислений |
|
DEFINE |
Глобальная для сессии |
Только строки |
Для подстановки значений в запросы |
|
BIND |
Глобальная для сессии |
Сложные типы данных |
Для работы с результатами PL/SQL и сложными данными (например, курсоры) |
Вывод данных с помощью DBMS_OUTPUT
Для вывода данных из переменных в PL/SQL можно использовать пакет DBMS_OUTPUT. Пример:
DECLARE some_var VARCHAR2(32); BEGIN SELECT some_col1 INTO some_var FROM some_tab WHERE some_col2 = some_filter; DBMS_OUTPUT.PUT_LINE('Our value is ' || some_var); END;
Этот код выполнит запрос и выведет результат в окне вывода, используя процедуру PUT_LINE.
______________________________________________________________________________
Общие рекомендации
-
Использование якорных типов: рекомендуется использовать %TYPE для переменных, чтобы избежать ошибок при изменении структуры базы данных.
-
Поддержка констант: константы помогают избежать случайных изменений данных, особенно если скрипт используется в разных частях программы.
-
Понимание областей видимости переменных: правильное использование переменных в разных областях (локальные в PL/SQL, глобальные в сессии) поможет оптимизировать выполнение программы.
-
DBMS_OUTPUT: пакет DBMS_OUTPUT полезен для отладки и вывода значений переменных в процессе выполнения программ.
Примечание. В SQL Developer для вывода put_line в окошко необходимо предварительно установить
SET serveroutput ON
______________________________________________________________________________
Резюме
В статье не были рассмотрены сложные типы данных, не рассказано о курсорах, триггерах. Нет достаточного многих важных аспектов PL/SQL.
Почему? Статья родилась из моих заметок, которые составлял в процессе самостоятельного изучения темы, а именно из первого блока, аля “новичкового”.
Планирую структуризировать дальнейшие заметки и составить еще несколько статей в том же обзорном ключе.
ссылка на оригинал статьи https://habr.com/ru/articles/862438/
Добавить комментарий