(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Подробности Oracle

Источник: oracle
Александр Просторов

Этот текст задуман как сборник неочевидных фактов о сервере Oracle, полезных и интересных разработчику. Своим появлением он обязан в первую очередь компании "МВ" - в течение некоторого времени я читал там курс основ программирования для Oracle, и в ходе подготовки к нему выяснил, что несколько утверждений, которые я полагал очевидными фактами, на самом деле не только не очевидные, но даже и не факты. В результате я решил собрать в одном месте то, что выпадает из поля зрения при беглом знакомстве, но может привести к неожиданным и неприятным последствиям.

К сожалению, "по заказу" вспомнить все такие мелочи - довольно тяжело; обычно они всплывают в памяти по мере необходимости. Поэтому эта статья еще очень долго будет пополняться, и поэтому я прошу присылать мне описания того, что я не знаю или забыл упомянуть. И - работая, старайтесь страховаться от подобных тонкостей.

Утверждения этой статьи проверены в основном на сервере Oracle 9i. В то же время я не проводил проверки, начиная с какой версии справедливо то или иное утверждение.

Оглавление.

  • Двойная группировка
  • Последовательности
  • Русские буквы
  • Исключения в declare
  • Оптимизатор
  • Операции с NULL
  • Размножение строк в connect by
  • Сочетание where с connect by
  • Natural Join

Двойная группировка.

Как и следует ожидать, Oracle поддерживает использование агрегатных функций в двух режимах - совместно с предложением GROUP BY, либо без оного. В последнем случае вся исходная выборка считается одной группой, а результат всегда состоит из одной строки. Интересно же то, что кроме этих двух вариантов, сервер поддерживает вложенные вызовы агрегатных функций - как во втором операторе примера:

SQL> select owner, count(*) 
		from dba_objects 
		where owner like '%SYS%' 
		group by owner ; 

OWNER                           COUNT(*) 
------------------------------ ---------- 
SYS                              13681 
SYSTEM                           332 
WMSYS                            131 

SQL> select max (owner), max (count(*)) 
		from dba_objects 
		where owner like '%SYS%' 
		group by owner ; 

MAX(OWNER)                       MAX(COUNT(*)) 
------------------------------ ------------- 
WMSYS                            13681

Результат вложенного вызова вычисляется как если бы внешняя агрегатная функция применялась к результату подзапроса, содержащего внутреннюю агрегатную функцию и выражение GROUP BY. То есть, в примере сервер сначала вычисляет COUNT(*) в выборке, сгруппированной по схемам, а затем применяет функцию MAX к результатам этой выборки, уже без дополнительной группировки.

Последовательности.

Неожиданной особенностью последовательностей является то, что атрибут NEXTVAL, возвращающий очередное значение последовательности, вычисляется не при каждом использовании. Упомянутый в SQL-операторе, скажем, в SELECT, он вычисляется только один раз для каждой очередной строки результата.

SQL> create sequence s1 ; 

Sequence created 

SQL> create sequence s2 increment by 2 ; 

Sequence created 

SQL> select s1.nextval, s1.currval, s1.nextval, s2.currval, s2.nextval, s2.nextval 
	from all_objects 
	where rownum <= 10; 

NEXTVAL CURRVAL NEXTVAL CURRVAL NEXTVAL NEXTVAL 
------- ------- ------- ------- ------- ------- 
   1       1       1       1       1       1 
	  2       2       2       3       3       3 
   3       3       3       5       5       5
   4       4       4       7       7       7 
   5       5       5       9       9       9
   6       6       6       11      11      11 
   7       7       7       13      13      13
   8       8       8       15      15      15 
   9       9       9       17      17      17
   10      10      10      19      19      19

Алгоритм действий сервера выглядит следующим образом: если используется атрибут NEXTVAL, очередное значение вычисляется один раз для каждой строки и подставляется в каждое место, где упоминается атрибут NEXTVAL или CURRVAL соответствующей последовательности. В том числе, не является ошибкой упоминание атрибута CURRVAL перед NEXTVAL в пределах одного оператора; все равно новое значение будет сначала вычислено и только потом подставлено. В то же время это не дает возможности в одной строке сослаться на старое значение последовательности и тут же вычислить новое.

Аналогичное правило действует и в случае других операторов SQL:

SQL> create table seq_values ( s11 number(3), s12 number(3),                                
								s21 number(3), s22 number(3));
Table created

SQL> insert into seq_values      
     values ( s1.nextval, s1.nextval, s2.nextval, s2.nextval ) ;

1 row inserted

SQL> insert into seq_values      
     values ( s1.nextval, s1.nextval, s2.nextval, s2.nextval ) ;

1 row inserted

SQL> insert into seq_values      
     select s1.nextval, s1.nextval, s2.nextval, s2.nextval from dual ;

1 row inserted

SQL> select * from seq_values ; 

S11  S12  S21  S22
---- ---- ---- ----  
11   11   21   21  
12   12   23   23  
13   13   25   25

SQL> update seq_values set     
   s11 = s1.nextval, s12 = s1.nextval ;

3 rows updated

SQL> select * from seq_values ; 

S11  S12  S21  S22
---- ---- ---- ----  
14   14   21   21  
15   15   23   23  
16   16   25   25

Русские буквы.

Oracle опередил большинство других инструментальных средств в сомнительной практике использования национальных символов в идентификаторах. Последствия такого решения видны на следующем примере:

SQL> create table dic$currencies ( currency_id integer, currency varchar2(3)) ;

Table created

SQL> select * from diс$currencies ;

ORA-00942: table or view does not exist

Причина ошибки в том, что одна из букв "с" в названии таблицы - русская. Сервер разрешает использование символов национальных алфавитов в идентификаторах без их заключения в кавычки; допускается создание названных по-русски таблиц, колонок, представлений и прочих элементов БД. К сожалению, исправление опечаток (допущенных при создании объекта, особенно при проектировании в CASE-средствах) приводит к подобным "странным" проблемам; после нескольких подобных случаев лично я создал системный триггер, запрещающий использование русских букв в создаваемых объектах.

Исключения в declare.

Блок операторов в Oracle состоит из трех секций, обрамленных ключевыми словами declare, begin, exception и end. Несколько неожиданный, хотя логичный факт - операторы обработки исключений в секции exception действуют для кода, выполняемого в секции begin но не действуют для кода, выполняемого в секции declare.

SQL> declare       
	s varchar2(1) := '12345' ;     
	
	begin       
		dbms_output.put_line ( 'Все в порядке' ) ;     
	exception     
	    when others then      
		  dbms_output.put_line ( 'Ошибка: ' // sqlerrm ) ;     
	end ;     
	/
	
	ORA-06502: PL/SQL: numeric or value error: character string buffer too small
	ORA-06512: at line 2

Эту особенность следует иметь в виду, применяя в declare сложную инициализацию - например, вызов функций, способных привести к исключению. Такое исключение может быть обработано только внешним по отношению к declare блоком - либо в вызывающей подпрограмме, либо в дополнительном, объемлющем блоке begin/exception/end.

Оптимизатор.

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

SQL> select count(*)      
  from ( select 1/0 from dual ) ;  

COUNT(*)
----------          
   1

Execution Plan
----------------------------------------------------------   
0      SELECT STATEMENT Optimizer=CHOOSE   
1    0   SORT (AGGREGATE)                                 
2    1     TABLE ACCESS (FULL) OF 'DUAL'

В то же время не стоит чрезмерно полагаться на эту способность; такой результат получается далеко не всегда:

SQL> declare       
	  i integer ;     
	begin       
	  i := 1/0 ;       
	  dbms_output.put_line ( 'Переменная i нигде больше не используется' ) ;     
	end ;     
	/

ORA-01476: divisor is equal to zero
ORA-06512: at line 4

Операции с NULL.

Для проверки знания особенностей операций с NULL я предлагаю Вам предсказать результаты выполнения следующего скрипта:

declare  


i integer ;  


procedure write ( expr varchar2 ) is  
begin    
	dbms_output.put_line ( expr ) ;  
end ;    


procedure test ( expr varchar2, value varchar2 ) is  begin    
	write ( rpad ( expr, 15 ) // ' = ' // coalesce ( value, 'null' )) ;  
end ;    


procedure test ( expr varchar2, value boolean ) is  
begin    
test ( expr, case when value then 'true'
			when not value then 'false'                      
			when value is null then 'null'                      
			else 'something strange' end ) ;  
end ;  
	
begin  
write ( '' ) ;  
test ( '1 + null', 1 + null ) ;  
test ( '2 * null', 2 * null ) ;  
test ( '3 // null', 3 // null ) ;  
write ( '' ) ;  
test ( 'true and null', true and null ) ;  
test ( 'false and null', false and null ) ;  
test ( 'true or null', true or null ) ;  
test ( 'false or null', false or null ) ;  
write ( '' ) ;  
test ( '(null = null)', null = null ) ;  
test ( '(null <> null)', null <> null ) ; 
 test ( 'not (null)', not ( null )) ;  
 write ( '' ) ;  
 test ( '(true > false)', true > false ) ;  
 test ( '(true > null)', true > null ) ;  
 test ( '(true >= null)', true >= null ) ;   
 write ( '' ) ; 
  test ( 'length (null)', length ( null )) ;  
 test ( 'length ('''')', length ( '' )) ;  
 test ( 'lpad ('''', 0)', lpad ( '', 0 )) ;  
 write ( '' ) ;  
 select ( select 1 from dual where 1 = 0 ) into i from dual ;  
 test ( 'i (select)', i ) ;  write ( '' ) ;  
 if null     
	then write ( '"if null then" works' ) ;    
 else write ( '"if null else" works' ) ;  
 end if ;
 end ;

Интересный момент в результатах этого скрипта - видно использование трехзначной логики при вычислении логических выражений. В то же время, пожалуй, вряд ли стоит строить приложения, рассчитанные на те или иные тонкости работы с null (особенно на пресловутый результат length ('')); скорее стоит максимально использовать специальные операции - is null, is not null, coalesce.

Размножение строк в connect by.

Обычно иерархический запрос можно считать сочетанием фильтра (на попадание строки в дерево) и упорядочивания (в порядке вершин дерева). Однако, эта модель становится неверной, как только условие в connect by позволяет привязать узел сразу к нескольким родительским узлам.

SQL> create table tree as     

select 1 id, cast ( null as integer ) parent_id from dual     
union all select 2 id, 1 parent_id from dual     

union all select 3 id, 1 parent_id from dual ;

Table created

SQL> insert into tree select * from tree ;

3 rows inserted

SQL> select *     

		from tree     
		start with id = 1     
		connect by prior id = parent_id ;

ID PARENT_ID
-- --------- 
1     
2     1
3     1
2     1
3     1 
1     
2     1
3     1
2     1
3     1    
 
10 rows selected

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

Сочетание where с connect by.

В иерархическом запросе (start with..connect by) построение дерева выполняется как часть предложения where. Порядок действий при этом следующий (см. *):

  • Выполняются указанные в where соединения таблиц
  • Полученная выборка обрабатывается согласно условиям start with..connect by
  • Применяются ограничения в where, не связанные с соединением таблиц.

Последствия этого можно увидеть на следующем примере:

SQL> create table tree as     
		select rownum id,            
		case when rownum = 1 then null              

		else round ( dbms_random.value ( 1, rownum - 1 )) end parent_id,            
		mod ( rownum, 3 ) modulo            
		from dba_objects            
		where rownum <= 10 ;

Table created

SQL> create table modulos as     

		select id, modulo from tree where modulo = 0 ;
		Table created

SQL> select *     
		from tree     

		where modulo = 0     
		start with id = 1     
		connect by prior id = parent_id ;

ID PARENT_ID MODULO
-- --------- ------ 
3         1      0 
9         3      0 
6         1      0

SQL> select t.*     
		from tree t, modulos m     

		where t.id = m.id     
		start with t.id = 1     
		connect by prior t.id = t.parent_id ;

ID PARENT_ID MODULO
-- --------- ------

SQL>

Здесь дважды выполняется почти одно и то же действие - но наличие соединения таблиц приводит к другому результату, поскольку фильтрация выполняется до выполнения connect by, а не после.

В целом, сочетание where с connect by - довольно странная мысль; если подумать, окажется, что какого-то очевидно правильного, единственно разумного пути действий в такой ситуации просто нет. Oracle выбрал путь, который можно назвать "угадыванием желаний"; путь, который будет правилен для большинства подразумеваемых запросов.

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

SQL> select t.*, m.*     
		from tree t, modulos m     
		where t.id = m.id or t.id = 1     
		start with t.id = 1     
		connect by prior t.id = t.parent_id ;   
     
 ID       PARENT_ID     MODULO      ID MODULO                          
---------- ---------- ---------- --------------------                                  
 1             1          3          0                                   
 1             1          6          0                                   
 1             1          9          0                         
  
Execution Plan
----------------------------------------------------------                        
0      SELECT STATEMENT Optimizer=CHOOSE                                        
1    0   FILTER                                                                 
2    1     CONNECT BY (WITHOUT FILTERING)                                       
3    2       COUNT                                                              
4    3         NESTED LOOPS                                                    
5    4           TABLE ACCESS (FULL) OF 'MODULOS'                               
6    4           TABLE ACCESS (FULL) OF 'TREE'

Отсюда видно, что Oracle действует таким же образом: выполняет соединение таблиц до построения дерева и фильтрует результат после построения. Сам по себе, однако, результат весьма неожиданный. Условие "t.id = 1", вроде бы привязанное логическим "или", на самом деле стало обязательным для всех строк результата - поскольку это единственное условие, выполняющееся после построения дерева. Этот запрос оказывается эквивалентным примерно следующему:

select * 
from ( select *        
		from ( select t.*, m.id m_id, m.modulo m_modulo              
			from tree t join modulos m on ( t.id = m.id )                
			union              
			select t.*, m.id m_id, m.modulo m_modulo              
			from tree t cross join modulos m               
			) t      
		 start with t.id = 1       
 	 connect by prior t.id = t.parent_id ) t

where t.id = 1

С моей точки зрения, этот пример показывает, что при сочетании where с connect by следует досконально понимать логику работы сервера, но лучше - позаботиться о явном, однозначном выражении своих мыслей с помощью подзапросов. Второй из этих вариантов может быть и не более читаем, нежели первый - но гораздо менее коварен.

Заодно, пожалуй, это показывает, почему в Oracle 8 запрещалось сочетание connect by и соединения таблиц в одном запросе.

Natural Join.

Пожалуй, из общих соображений natural join следует назвать сомнительной операцией. Она может быть удобна для администрирования, для разовых запросов в случае, если администратор знает, что две таблицы соединяются но не помнит, как именно называется связующее поле. Разработчик же вряд ли имеет право применять эту операцию в программном коде: любое изменение структуры базы может привести к незаметному изменению фактически выполняемого запроса. Добавления в таблицу поля с названием "name" или "date_start" окажется вполне достаточно, чтобы разрушить логику работающего приложения и вполне вероятно, нанести тяжелые повреждения данным.

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

SQL> select d.*, s.sid, s.username, s.program      
from dual d natural join v$session s ;

DUMMY  SID USERNAME                       PROGRAM
----- --- ------------------------------ -----------
X       1                                ORACLE.EXE
X       2                                ORACLE.EXE
X       3                                ORACLE.EXE
X       4                                ORACLE.EXE
X       5                                ORACLE.EXE
X       6                                ORACLE.EXE
X       7                                ORACLE.EXE
X       8 TEST                           
X       9 TEST                           
X      11 TEST                           
X      12 TEST                           sqlplus.exe
X      13 TEST                           sqlplus.exe
X      14 TEST                           
X      15 TEST                           
X      16 TEST                           
X      17 TEST                           

16 rows selected

Таким образом, особенно если запрос содержит дальнейшую группировку, легко незаметно получить неверные данные (ошибочно связав не имеющие связи таблицы). Из-за этого вряд ли стоит использовать natural join вообще; для экономии работы пальцами, пожалуй, стоит дождаться операции наподобие foreign key join - ее можно определить достаточно надежно.

(*) В документации Oracle этот момент сформулирован весьма невнятно и допускает совершенно разные толкования. Изложенное - результат некоторых экспериментов, а также уверенности, что текст документации можно понять и таким образом.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 09.09.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Oracle Database Personal Edition Named User Plus Software Update License & Support
Oracle Database Standard Edition 2 Named User Plus License
Oracle Database Standard Edition 2 Processor License
Oracle Database Personal Edition Named User Plus License
GFI LanGuard подписка на 1 год (25-49 лицензий)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
eManual - электронные книги и техническая документация
Один день системного администратора
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100