Несомненно, в числе первых проблем, с которыми вы столкнетесь при локализации своих приложений PL/SQL, будет поддержка многобайтовых символов. Когда вы передадите первый японский символ в переменную VARCHAR2
и получите ошибку ORA-6502
, скорее всего, час-другой будет потрачен на отладку процедуры, которая «должна работать».
Возможно, в какой-то момент выяснится, что для поддержки многобайтовых наборов символов вам придется изменить все объявления всех символьных переменных или символьных столбцов в вашем приложении PL/SQL. Не отчаивайтесь! После решения всех начальных проблем значительно упростится управление реализацией приложения в будущем. Рассмотрим пример:
DECLARE
v_title VARCHAR2 (30);
BEGIN
SELECT title
INTO v_title
FROM publication
WHERE publication_id = 2;
DBMS_OUTPUT.put_line (v_title);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
Программа выдает следующее исключение:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
Проблема в том, что точность переменной составляет 30 байтов, а не символов. Во многих азиатских наборах представление одного символа занимает до 3 байтов, поэтому в переменную длины 2 не поместится ни один символ выбранного набора!
При помощи функции LENGTHB
мы можем определить фактический размер строки:
DECLARE
v_length_in_bytes NUMBER (2);
BEGIN
SELECT LENGTHB (title)
INTO v_length_in_bytes
FROM publication
WHERE publication_id = 2;
DBMS_OUTPUT.put_line ('String size in bytes: ' || v_length_in_bytes);
END;
Результат:
String size in bytes: 52
До выхода Oracle9i
наши возможности были ограничены. Самым распространенным решением в Oracle8i
было простое умножение максимального количества символов на 3:
DECLARE
v_title VARCHAR2 (90);
BEGIN
SELECT title
INTO v_title
FROM publication
WHERE publication_id = 2;
DBMS_OUTPUT.put_line (v_title);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
Если в вашей системе необходимые глифы могут быть выведены на экран, возвращается следующий результат:
Oracle PL/SQLプログラミング 基礎編 第3版
Обходное решение работает, но выглядит весьма неуклюже. Байтовая семантика и умножение на 3 приводит к нежелательным последствиям для вашего приложения:
- Многие фирмы-разработчики СУБД по умолчанию используют символьную семантику вместо байтовой, что затруднит возможное портирование приложения.
- Если символы занимают не все 3 байта, в переменной или столбце может быть сохранено больше символов, чем предполагалось.
- Автоматическое дополнение типов данных
CHAR
вOracle
означает, что все 90 байт будут зарезервированы в памяти — независимо от того, используются они или нет. Символьная семантика впервые появилась вOracle9i
. При объявлении переменной размер может задаваться как в байтах, так и в символах. Следующий пример почти полностью совпадает с неудачным примером, приводившимся ранее, — с одним исключением. Взгляните на объявление переменной, чтобы понять, как активизируется символьная семантика:
DECLARE
v_title VARCHAR2 (30 CHAR);
BEGIN
SELECT title
INTO v_title
FROM publication
WHERE publication_id = 2;
DBMS_OUTPUT.put_line (v_title);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
На этот раз программа выводит полную строку:
Oracle PL/SQLプログラミング 基礎編 第3版
Впрочем, и этот метод требует модификации каждой символьной переменной или объявления столбца в вашем приложении. Проблема проще решается переходом от байтовой семантики к символьной на уровне всей базы данных. Для этого достаточно присвоить переменной NLS_LENGTH_SEMANTICS
значение CHAR
. Текущее значение параметра можно узнать при помощи следующего запроса:
SELECT parameter, VALUE
FROM nls_session_parameters
WHERE parameter = 'NLS_LENGTH_SEMANTICS'
Результат выполнения:
PARAMETER VALUE
------------------------- ----------
NLS_LENGTH_SEMANTICS BYTE
Также информацию можно получить из представления V$PARAMETER
:
SELECT NAME, VALUE
FROM v$parameter
WHERE NAME = 'nls_length_semantics'
Запрос возвращает следующую информацию:
NAME VALUE
------------------------- ----------
nls_length_semantics BYTE
Для изменения значения NLS_LENGTH_SEMANTICS
используется команда ALTER SYSTEM
:
ALTER SYSTEM SET NLS_LENGTH_SEMANTICS = CHAR
Команда ALTER
SESSION
изменяет значение параметра на время текущего сеанса:
ALTER SESSION SET NLS_LENGTH_SEMANTICS = CHAR
При таком подходе модификация существующего приложения выполняется проще простого; все существующие объявления автоматически интерпретируются в символах вместо байтов. После перевода системы на символьную семантику изменения отражаются в словаре данных:
SELECT parameter, value
FROM nls_session_parameters
WHERE parameter = 'NLS_LENGTH_SEMANTICS'
Результат:
PARAMETER VALUE
------------------------- ----------
NLS_LENGTH_SEMANTICS CHAR
Возвращаясь к предыдущему примеру, мы видим, что символьная семантика применяется без включения ключевого слова CHAR
в объявление.
DECLARE
v_title VARCHAR2 (30);
BEGIN
SELECT title
INTO v_title
FROM publication
WHERE publication_id = 2;
DBMS_OUTPUT.put_line (v_title);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
Результат будет таким:
Oracle PL/SQLプログラミング 基礎編 第3版
Обратите внимание: максимальный размер в байтах не изменяется при активизации символьной семантики. Хотя символьная семантика позволит разместить 1000 3-байтовых символов в переменной VARCHAR2(1000)
без изменений, поместить 32,767 3-байтовых символов в переменной VARCHAR2(32767)
вам не удастся.
Предельный размер переменной VARCHAR2
по-прежнему составляет 32 767 байт, а столбца VARCHAR2
— 4000 байт.
Учитывая символьную семантику при проектировании приложения, вы сильно упростите себе жизнь. Если только у вас нет веских причин для использования байтовой семантики в части вашего приложения, задайте параметр NLS_LENGTH_ SEMANTICS
= CHAR
, чтобы символьная семантика использовалась по умолчанию. Если вы измените настройку NLS_LENGTH_SEMANTICS
для существующего приложения, не забудьте перекомпилировать все объекты, чтобы изменения вступили в силу. В частности, вам придется заново выполнить сценарий catproc.sql
, чтобы повторно создать все пакеты!