Базы данных Oracle - статьи

         

Имена объектов, ключевые и зарезервированные слова


История этой задачки началась с форума Oracle на , куда я однажды случайно забрел. Один из участников форума пытался перенести в Oracle схему БД, подготовленную в другой системе. В таблице было поле, названное NUMBER. Очевидно, в нем хранилось какое-то число; с этим-то полем в Oracle и случилась незадача. Ее раскрывает следующий (уже мой) пример:

SQL> CREATE TABLE t(number NUMBER); CREATE TABLE t(number NUMBER) * ERROR at line 1: ORA-00904: : invalid identifier

Автор вопроса правильно сообразил, что NUMBER - это зарезервированное в Oracle слово (мы помним, что перед обработкой предложения SQL Oracle повышает регистр букв в именах объектов), и ошибка возникает при синтаксическом разборе. В документации про ошибку ORA-00904 сказано: "… [column name] may not be a reserved word". Раз уж говорим о заморочках, обратите внимание на текст сообщения: между двумя двоеточиями по замыслу разработчиков должно стоять то самое неправильное имя, а в нашем случае оно оказалось настолько неправильным, что не попало в результат синтаксического анализатора ! Не самый большой грех в Oracle, но ведь по теме статьи.

Неужели перенося базу в Oracle придется править схему, а значит и приложение ? Если не выход, то лазейку из положения автор вопроса в форум нашел правильно. Заключив имя объекта в Oracle в двойные кавычки мы приобретем право использовать абсолютно любые доступные в кодировке символы, хоть звездочки, хоть точки, хоть пробелы:

SQL> CREATE TABLE t(" ./()*" NUMBER, "NUMBER" NUMBER);

Table created.

SQL> DESCRIBE t Name Null? Type ------------------- -------- ---------------------- ./()* NUMBER NUMBER NUMBER

SQL> ALTER TABLE t DROP COLUMN " ./()*";

Table altered.

SQL> DESCRIBE t Name Null? Type ------------------- -------- ---------------------- NUMBER NUMBER

Если смириться с неудобством двойных кавычек, это вполне рабочий вариант:

SQL> INSERT INTO t VALUES (123);

1 row created.

SQL> UPDATE t SET "NUMBER" = "NUMBER";


1 row updated.



Беда в том, что точно та же команда UPDATE, равно как и любая другая, обращающаяся к столбцу "NUMBER", не работает в PL/SQL:

SQL> BEGIN UPDATE t SET "NUMBER" = "NUMBER"; END; 2 / BEGIN UPDATE t SET "NUMBER" = "NUMBER"; END; * ERROR at line 1: ORA-06550: line 1, column 53: PL/SQL: ORA-06552: PL/SQL: Compilation unit
analysis terminated ORA-06553: PLS-320: the declaration of the type
of this expression is incomplete or malformed ORA-06550: line 1, column 25: PL/SQL: SQL Statement ignored

Это и вызвало недоумение автора вопроса в форум. В Oracle уверяют, что начиная с версии 9 (которая помогала готовить эту статью) обработка запросов в SQL и в PL/SQL ведется одним и тем же модулем СУБД. И тем не менее факт налицо: SQL терпит обращение к полю "NUMBER", а PL/SQL - нет.

Заинтересовавшись, после серии экспериментов я нащупал очередную уступку Oracle, позволившую достичь компромисса, но сообщить найденное решение автору вопроса уже не смог, так как sql.ru оказался для меня слишком сложно устроен: он не только требует регистрации (а придуманные себе кличку и пароль я назавтра же забыл), но и содержит чересчур много "нитей", среди которых "свою" я безнадежно потерял.

Вот что выяснилось: PL/SQL начинает все обрабатывать правильно, как только в названии столбца появляется хотя бы одна малая буква, например, не "NUMBER", а "NuMBER". Лучше, конечно, "number". Возникает вопрос: в чем причина такого странного поведения ?

Переписка с разработчиками PL/SQL несколько прояснила ситуацию. Можно посочувствовать: им приходится расплачиваться по счетам самой логики жизни. В любом языке есть множество зарезервированных слов, которые нельзя использовать для имен объектов. В Oracle первую очередь это ключевые слова: BEGIN, SELECT и другие. Заметьте кстати, что множества зарезервированных слов Oracle в SQL и PL/SQL не совпадают:

SQL> CREATE TABLE t1 (begin NUMBER);



Table created.

SQL> UPDATE t1 SET begin = 1;

0 rows updated.

SQL> BEGIN UPDATE t1 SET begin = 2; END; 2 /

PL/SQL procedure successfully completed.

SQL> DECLARE begin NUMBER; BEGIN NULL; END; 2 / DECLARE begin NUMBER; BEGIN NULL; END; * ERROR at line 1: ORA-06550: line 1, column 38: PLS-00103: Encountered the symbol "end-of-file"
when expecting one of the following: ... ... ... ...

(Но, конечно же:

SQL> DECLARE "begin" NUMBER; BEGIN NULL; END; 2 /

PL/SQL procedure successfully completed.

)

Однако жизнь бывает сложнее, чем иногда хотелось бы. Вопреки утверждению документации по Oracle (сказали разработчики PL/SQL) не все ключевые слова являются зарезервированными. Вызвано это тем, что язык живет, и в нем могут появляться новые ключевые слова, которые кто-то, когда они еще не были ключевыми, мог использовать для названий объектов. Запрет вдруг их такового употребления способен был бы вызвать возмущение пользователей, принужденных переделывать имеющиеся БД и приложения. Отсюда и отклонения от идеального равенства "ключевые слова" = "зарезервированные слова". Нагладный пример: до версии 9 в Oracle не было типа TIMESTAMP, и многие использовали это слово для именования поля в таблице. В версии 9 эту вольность (по законам новой версии) употребления пришлось оставить, хотя и удалось это не совсем последовательно:

SQL> ALTER TABLE t1 ADD (timestamp TIMESTAMP);

Table altered.

SQL> UPDATE t1 SET timestamp = SYSTIMESTAMP;

SQL> DECLARE t TIMESTAMP; BEGIN t := SYSTIMESTAMP; END; 2 /

PL/SQL procedure successfully completed.

SQL> DECLARE timestamp NUMBER; BEGIN timestamp := 1; END; 2 /

PL/SQL procedure successfully completed.

но, правда

SQL> DECLARE timestamp TIMESTAMP; BEGIN timestamp := NULL; END; 2 / DECLARE timestamp TIMESTAMP; BEGIN timestamp := NULL; END; * ERROR at line 1: ... ... ... ...

Есть в Oracle и другое правило, уже наблюдавшееся выше по тексту: и в SQL, и в PL/SQL заключение имени объекта в двойные кавычки сравнение со списком зарезервированных слов отменяет:

SQL> DECLARE "timestamp" TIMESTAMP; BEGIN "timestamp" := NULL; END; 2 /

PL/SQL procedure successfully completed.

Это и объясняет срабатывание примера, с которого начался этот раздел, при замене "NUMBER" вместо NUMBER.

Все это можно понять. А остался ли за Oracle какой-нибудь грех ? Не без того. Разработчики PL/SQL признались: несмотря на то, что с версии 9 обработка SQL в PL/SQL и в качестве самостоятельных команд происходит одинаково, список зарезервированных слов в PL/SQL свой (по-прежнему) собственный, и, как оказалось, не всегда совпадает со аналогичным списком в SQL там, где он бы должен совпадать. Это и привело к непредусмотренному архитекторами Oracle результату при попытке обратиться к полю "NUMBER" в PL/SQL. Иными словами, это ошибка разработчиков, которая сохранилась в нынешней версии 10.1, и которую пообещали исправить в будущем.


Содержание раздела