WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 10 | 11 || 13 | 14 |   ...   | 24 |

«Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...»

-- [ Страница 12 ] --

Предметный указатель А Автономная транзакция 160 Администратор базы данных 18 сфера деятельности 94 Анонимный блок PL/SQL 183 Архитектура MTS 44, 47 двухзадачная 118 двухпроцессная 118 многопотокового сервера 44 Атрибут FIX 477, 479 использование 479 NOLOGGING 96 STR 477 использование 485 VAR 477 использование 484 Б База данных 80, 8 1, 92 виртуальная приватная 18 идентификатор 85 подключение через выделенный сервер 86 разделяемый сервер 86 таблицы транзакций 157 Библиотечный кэш 48, 73, 565 Блок 90, 92, 344 служебное пространство 91 данных 56 заголовок 271 кода PL/SQL добавление обработчика исключительных ситуаций 185 листовой 344 Блокирование 76 вручную 163 в СУБД Oracle 52 назначение 52 на уровне страниц 138 на уровне строк 138 оптимистическое 143 пессимистическое 142, 144 преимущества 144 принципы 52 проблемы 140 возникновение 148 способы реализации 52 Блокировка 51, 130, 596 OPS 151 - специфическая 151 ROW EXCLUSIVE TABLE 151 ROW SHARE TABLE 151 TM 160, 596, 165 низкоуровневая 160 особенности 159 взаимная 146, 378 внутренняя 151, 163 глобальная 130 исключительная 151, 154, 155 запрос 155 использование 138 как атрибут данных 152 определяемая пользователем 163 очередность ЯМД 158 разбора 160, 162 распределение 152 способы 138 таблицы 57 тип 151 транзакций 152 чтения 52, 59 эскалация 52, 150 ЯМД 151, 152 ЯОД 151, 159, 165 исключительная 159 разделяемая 160 Большой пул 107 использование 114 Буфер журнала повторного выполнения 108 сброс 199 Буферный кэш 97 использование Предметный указатель в Взаимная блокировка 146, 172, 173 причина 148 Виртуальная операционная система 39 Внешний ключ неиндексированный 147, 176, 378 нахождение 148 проблемы 150 не проиндексированный 378 Восстановление 421 Временная таблица 62, 63, 236 Время отката 212 Вставка множественная 227 непосредственная 234 Вторичные файлы данных 502 сложные 504 Выгрузка данных 487 Выделенный сервер режим 116 использование 116 Выполнение запроса основные стадии 553 Выражение CASE 63 Высота дерева блокировок традиционный 152 восстановление 133, 395, 415 распределенных блокировок 130 Домен 63 Доступ 51 по индексу 358 Доступность данных увеличение 160 Ж Жесткий разбор 531 сокращение количества 73 Журнал активный 99 архивный 99 переключение 97 понимание 197 повторного выполнения 57, 94, 97, 234 размещение 234 сообщений 97, 228 только для записи 200 транзакций 206 Журнальный файл 466 3 Заголовок блока 91 Загрузка больших объектов 498, 506 из внешних файлов 503 вложенных таблиц 508 в фиксированном формате 462 из отчетов 473 массивов переменной длины 508 множественная 234 непосредственная 234, 452 обычная 452 данных с разделителями 458 содержащих символы новой строки 477 файлов в формате dBASE 516 Запись APPNAME 562 BINDS 565 с детальной информацией 566 CURSOR 562 ERROR 572 EXEC 563 PARSE 565 PARSE ERROR Г Глобальная область пользователя 100, 116 процесса 100 системы 100 Группировка 376 Группа dba 89 Грязное чтение 166, д Данные отмены 94, 197 повторного выполнения 94, 205 анализ 240 способ определения объема 215 Двухэтапная фиксация 127, 180 Действие CONTINUE_LOAD 456 Директива IFILE 90 INFILE 477 Диспетчер 84 MTS Предметный указатель показатель кластеризации 358 по убыванию 343, 349 по функции 343, 372, 540 UPPER 365 использование 277, 363 предварительная настройка 363 пример использования 364 прикладной 343, 373 проверка использования 618 сжатия ключей 320 с обращенным ключом 342, 348, 349 со сжатием 346 составной 350 список свободных мест 388 структура 288 текстовый 343 эффективность 287 Индексирование 341 Интерфейс 452 ODBC 312 К Картридж 506 Каскадное изменение 186 удаление 150, 379 Каталог $ORAClE_HOME/dbs 89 $ORACLE_HOME/rdbms/admin 32, 161,436, 532,576, 632 %ORACLE.HOME%\OATABASE 89 [ORACLE.HOME]/sqlplus/admin 33 [ORACLE_HOME]/sqlplus/demo 30 [ORACLE_HOME]\network\admin 85 ORACLE_HOME 88 строк 91 таблиц 91 Категория 610 DEFAULT 620 Кластер 130, 258 C_OBJ# 297 в СУБД Oracle 290 загрузка таблиц 294 индекс по ключу 292 индексный 291 использование 296 параметры 292 Клонирование PARSE, FETCH, UNMAP и SORT UNMAP 564 PARSING IN CURSOR 566 STAT 569 WAIT 564 XCTEND 569 в фиксированном формате 451 с ограничителем 451 Запрос перехвата 613 Защелки 73, 151, 163 конфликты при установке 591 Защитное программирование 525 Значение CLUSTERING.FACTOR 357 SIZE 296 И Идентификатор объекта в кавычках использование 636 сгенерированный системой 334 Идентификаторы строк 290, 296 дублирующие 296 логические 290 Избыточность 130 Изолированность транзакции 179 уровни 165 Именованный канал 407 Импорт 398 в режиме SHOW 398 Импортирование в другие структуры 423 Имя объекта 557 получение по идентификатору 557 Индекс 410 Rtree 375 вторичный 289 избыточный 385 использование 382, 385 кластера 342 кластерный 279, 290 на основе В*-дерева 61, 342, 378 использование 351 обработка пустых значений 378 на основе битовых карт 343, 359, 361 не используется основные причины 385 неуникальный 344 обзор 342 обычный 343 по внешним ключам Предметный указатель Ключ кластера 258 индекса префикс 345 суффикс 345 Ключевое слово CAST 311 DESC 349, 350 DETERMINISTIC 367 FILLER 461, 509 INCLUDING 281 MULTISET 311 NOLOGGING 225 ONLINE 160 OVERFLOW 281 POSITION 462 SINGLE TABLE 306 UNRECOVERABLE 225 Команда COLUMN 34 copy 397 CREATE INDEX 418 describe 332 EXPLAIN PLAN 32 mknod 407 ps 82, 84, 127 set serveroutput on 569 Компонент 373 ActiveX 72 EJB 41, 44, 70 взаимодействие 122 критика 43 interMedia 373 Конструкторы объекта 331 Конструкция 88 AS SYSDBA 413 BEGINDATA 456 BETWEEN 280, 382 BULK COLLECT 555 CLUSTER 293 COLUMN OBJECT 507 CONNECT BY 66 COUNT 509 DISABLE STORAGE IN ROW 274 ENABLE STORAGE IN ROW 274 FIELDS TERMINATED BY 462 FOR UPDATE 59, 60, 151, 500 GROUP BY 376 IGNORE=YES INCLUDING 288 INFILE 456, 517 INITIALLY IMMEDIATE 187 INTO TABLE DEPT 456 NESTED TABLE 310 NOLOGGING 225 NOWAIT 158 OIDINDEX 333 ON COMMIT PRESERVE ROWS 322 ON DELETE CASCADE 379 OPTIONALLY ENCLOSED BY 483 ORDER BY 273 OVERFLOW 287 PCTTHRESHOLD 287 POSITION 462 RECNUM 474 RETURN AS VALUE 319 STORAGE для экспортируемых объектов 401 TERMINATED BY 460 TERMINATED BY EOF 504 TERMINATED BY WHITESPACE 460 VARRAY COUNT 509 WHERE 191 Контрольная точка 96 обработка 128 Конфигурация выделенного сервера 83 Конфликт при доступе к журналу 234 Координатор 127 распределенной транзакции 195 транзакции 127 Коэффициент попадания в библиотечный кэш 593 Куча 257 Кэш 112 словаря данных 112, 592 Л Листовые вершины 344 Локатор LOB 500 М Максимальный уровень 260 Маска формата даты 464 Масштабирование 65 Масштабируемость 49, 5 1, 62 Метод FULL SCAN random 304 SETPROPERTIES Предметный указатель SGA 109, 118 В ОС UNIX 105 назначение 84 очередь запросов 84 параметры основных компонентов 578 фиксированная 108 UGA 84, 103, 115, 116 Обработка контрольной точки 96, 228, выполнение очистки 230 прекращение 205 Обработчик исключительных ситуаций WHEN OTHERS 571 Обратный вызов 132 Объект SEQUENCE 273 Объектно-реляционные компоненты использование 336 Объектный идентификатор 333 изменение 333 тип 310, 506 Одновременный доступ 137 механизм обеспечения 51 особенности управления 51 непонимание 51 управление 54, 76 Ожидание событий 564 на уровне экземпляра 603 ООТ 24 Оператор 59, 610 ALTER INDEX REBUILD 387 ALTER OUTLINE 607, 629 ALTER SESSION 613, 614 выполнение 614 использование 624 ALTER SYSTEM 364 ALTER TABLE 228 DISABLE TABLE LOCK 159 ALTER TABLE MOVE 279, 423 изменение идентификаторов строк 279 ANALYZE 233, 253, 325 BEGIN WORK/COMMIT 188 BETWEEN 381 CASE 467, 468 использование 451 CAST 311 COALESCE 387 COMMIT 152, 180, 200, 207, 209, 210 время выполнения 212 выполнении SETROWPREFETCH 555 настройки 610 оценки побочных эффектов 220 Механизм организации очереди 153 повторного выполнения 207 Многовариантный доступ 165, 167 Многовариантная согласованность последствия использования 56 Многовариантность 54, 55, 57 демонстрация 58 Многопользовательский доступ 52 Модель блокирования последствия выбора 137 Модуль mod_plsql 579 использование 580 Моментальный снимок 131 Монитор процессов 125 системы 126 Мягкий разбор 532 сокращение количества Н Нарушаемые блокировки разбора 161 Настройка 42, 519, 603 метод 611 постфактум 341, 521 при наличии конкретной конечной цели 583 принцип 522 средства 572, 575 Неделимость 68, 179 оператора 181 последствия 181 Неповторяемость при чтении 166 Непосредственная загрузка 227 Непосредственные вставки 227 Непосредственный экспорт 428 Неудачи причина 40 Неформатированные устройства 236 "сложности" работы 236 Неявные преобразования О Область PGA 103, 104, Предметный указатель с опцией FORCE 196 CREATE 32, 157, 447, 619 CREATE CLUSTER 291 CREATE INDEX 225, 351 CREATE OR REPLACE OUTLINE 610 CREATE OUTLINE 610, 623 CREATE SNAPSHOT 67 CREATE TABLE 225, 272, 273 опции 274 CREATE TABLE AS SELECT 233 CREATE TABLE AS SELECT NOLOGGING 226 CREATE TEMPORARY TABLESPACE 94 CREATE VIEW 161 DELETE 56, 200 объем генерируемых данных повторного выполнения 223 заблокированный 145 из временной таблиц 239 DROP INDEX 225 DROP TABLE 158, 225, 240 DROP OUTLINE 631 EXPLAIN PLAN 558 GRANT 162, 32, 33 INSERT 59, 200, 210 INSERT INTO T 197 INSERT / * + APPEND */ 227 LOBFILE 504 LOCK TABLE 164 ROLLBACK 152, 180, 212 неявная установка 188 с опцией FORCE 196 ROLLBACK TO 181 SAVEPOINT 181, 182, 184, 185 неявная установка 188 SELECT 59, 168, 190 SELECT... FOR UPDATE 164, 175 SELECT FOR UPDATE NOWAIT 144 SET CONSTRAINT 188 SET TRANSACTION 181, 192, 241 указания сегмента отката 241 SYNONYM 32 TRUNCATE 240, 260, 516 использование 260 результат применения 260 UPDATE 191, 200 заблокированный 145 объем генерируемых данных повторного выполнения 224 использование параллельный пустой режим FIRST ROWS 588 создания связи 195 управления транзакцией 180 ЯМД 194 ЯОД 629 в удаленной базе данных 196 использование 623 невозможность выполнять 196 получение 415 Операционная система 38 Операция INSERT 457 REPLACE 457 TRUNCATE 457 Оптимизатор 73 предоставления информации 327 режим CHOOSE 588 Опция APPEND 470 COMPRESS 282 HASHKEYS 299 HELP = Y 400 NOCOMPRESS 282 NOLOGGING 227 правильное использование 228 способ использования 227 ON DELETE CASCADE 224 PCTTHRESHOLD 282 REPLACE 516 TRUNCATE 4 7 1, 516 Откат 205, 240 что происходит 212 Открытость 70 Отладка 572 Отметка максимального уровня 259 увеличение 260 Очистка блоков 242 механизм 249 отложенная 249 при фиксации 230 Ошибка ORA-01555 242 неоднозначность 247 при отложенной очистке блока 250 времени выполнения 570 разбора п Предметный указатель Пакет 632, 481 DBMS_JOB 481, 512 DBMS_LOCK 145, 164 создание собственных блокировок 164 DBMS_OUTPUT 30, 182 DBMS_PROFILER 572 использование 573 DBMS_ROWID 294 DBMS_SHARED_POOL 112 DBMS_SPACE 267, 300 DBMS_SQL 488 DBMS_STATS 325, 326, 329 экспортирование статистической информации 326 DEBUG 574 LOB_IO 488 OUTLN_PKG 607, 632, 635 STATSPACK 577 UTL_FILE 164, 488, 512, 516 настройка 488 Параметр % Blocks Changed per Read 579 BACKUP_TAPE_IO_SLAVES 133 COMPATIBLE 351 CREATE_STORED_OUTLINES 624, 642 CURSOR_SHARING 72, 73, 74, 535, 539, 540, 542 побочные эффекты использования 535 результаты установки 542 установка 545 'CURSOR_SHARING = FORCE' 637 DB_BLOCK_BUFFERS 231, 608 DB_NAME 95 DML_LOCKS 159 FILESIZE 405 использование 405 HASH_JOIN_ENABLED 627 HASHKEYS и SIZE 308 правильная установка значений 308 INCTYPE 401 INDEXFILE 415, 416 INITIAL, NEXT и PCTINCREASE 270 INITRANS 157, 271 LOGGING и NOLOGGING 271 MAXEXTENTS 271 MAXTRANS 157, 271 MINEXTENTS 271 OPTIMIZER_GOAL 631 ORACLE SID OWNER 275 PCTFREE 282 значения 263 установка значений 266 PCTFREE/PCTUSED 275 для данных больших объектов 275 использовании 289 PCTTHRESHOLD 288, 289 PCTUSED 263, 269 значения 263 последствия увеличения значения 269 установка значений 266 plsql_load_without_compile 89 prefetch=NN 555" QUERY 409 QUERY_REWRITE_ENABLED 364 QUERY_REWRITE_INTEGRITY 364 SIZE 292, 298 неправильное задание 298 SNAPSHOT_REFRESH_INTERVAL 131 SNAPSHOT_REFRESH_PROCE 131 Soft Parse % 589 Soft Parse Ratio 597 SORT_AREA_RETAINED_SIZE 115 SORT_AREA_SIZE 75 SQL_TRACE 302, 546 включить 547 TIMED_STATISTICS 546, 550, 553 USER_DUMP_DEST 550 USERID 401 инициализации 114, 522, 595, 616 CURSOR_SHARING 113 DB_BLOCK_MAX_DIRTY_TARGET 230 FAST_START_IO_TARGET 205 FAST_START_IO_TARGET 230 FAST=TRUE 527 LOG_CHECKPOINT_INTERVAL 230 LOG_CHECKPOINT_TIMEOUT 230 ORACLE_SID 125 RECOVERY PARALLELISM 205 SHARED_POOL_SIZE 114 официально поддерживаемый 89 хранения 291 Первичный ключ 142, 332 генерации 145 изменение 147, 186 объекта 333 фиктивный 332 Переключение журнала 96 Переменная среды Предметный указатель NLS_LANG 444 Перенос данных 410 строк 264 Пересоздание экземпляров 399 План выполнения изменение 558 Повторное выполнение 205, 206 Повторяемости при чтении 42, 43 Поддержка национальных языков 442 очередей 67 расширенная 68 реализация 67 Подзапрос 279 Подключение в режиме MTS 120 к выделенному серверу 120 Подсказка 315 INDEX() 622 NESTED_TABLE_GET_REFS 316, 317 использование 317 NO_EXPAND 622 NOREWRITE 621 ORDERED 622 RULE 621 оптимизатору 546 Подставляемое представление 149 Подход 519 к разработке 38, 40 по принципу "черного ящика" 40 Показатель кластеризации 357 Полный просмотр 150, 352 быстрый 352 по индексам 352 Пользователь SYS 161 Последовательность 64 Постфактум 603 Потери изменений предотвращение 172 Потерянное изменение 140 Представление ALL_OUTLIN 621 all_views 567 DBA_DDL_LOCKS 161 DBA_OUTLINE_HINTSALL_OUTUN 621 OUTLINES OUTLINE_HINTS 619 USER_NDEXES 357 USER_OUTLINES 615, 620 USER_TAB_COLUMNS 567 V$ 210, 519, 531, 574, 595 основные 595 V$EVENT_NAME 595 V$FILESTAT 385, 596 V$LOCK 596 V$MYSTAT 214, 596 V$OPEN_CURSOR 597 V$PARAMETER 550, 599 V$PROCESS 119 V$SESS_IO 602 V$SESSION 69, 119, 547, 562, 599 V$SESSION_EVENT 529, 584, 601 V$SESSION_LONGOPS 602 V$SESSION_WAIT 602 V$SESSTAT 602 V$SGASTAT 106, 108, 114 V$SQL 316, 603 V$SQLAREA 544, 563, 603 V$STATNAME 214, 603 V$SYSSTAT 603 V$SYSTEM_EVENT 603 V$TEMPSTAT 596 V$TRANSACTION 209 V_$STATNAME 596 динамической производительности 595 объектное 336 подставляемое 324 с подсказками 628 Преобразование блокировок 150 Привилегия ALTER ANY OUTLINE 622 CREATE ANY DIRECTORY 498 CREATE ANY OUTLINE 622, 637 CREATE OR REPLACE ANY OUTLINE 623 DROP ANY OUTLINE 622 EXECUTE ANY PROCEDURE 412 EXECUTE ON OUTLN_PKG 622 SELECT 493, 529, 609 системная 363 Приглашение 31 Приоритет точки фиксации 196 Проблема блокирования устранение 150 Программа Предметный указатель ora_ckpt_ora816dev 82 ora_lgwr_ora816dev 82 Oracle.exe 82 PMON 163 RECO 127 SNPn 124, 132 TNS Listener 85 глобальная область 116 диспетчеры MTS 84, 124 записи блоков базы данных 96, 128 блоков в базу данных 117 журнала повторного выполнения 125 сбоя 125 обработки контрольной точки 127 снимков 131 подчиненный 80, 117, 132 ввода/вывода 132 параллельных запросов 133 прослушивания 85 в режиме MTS 86 серверный 80, 117 фоновый 80, 82, 117, 123, 584 ожидания событий 584 служебные 131 схеме 123 Псевдостолбец 258 Пул 107 Java 115 KEEP 111, 114 RECYCLE 111, 114 большой 114 подключений GENCTL 477 tlist 82 Продолжительность 179 Проектирование с учетом производительности 523 Производительность 49, 51, 52, 62 критерии 526 проблемы 52 Пропускная способность 121 Просмотр диапазона по индексу 344 Пространство занятое 91 свободное 91 Протокол 2РС 195 НИР 72 ПОР 44 Internet Inter-Orb Protocol 44 Net8 44, 72 SSL 72 двухэтапной фиксации 195 Профиль 69 ресурсов 144 Профилировщик исходного кода 572 Процедура 252 DBMS.LOCK_SLEEP 252 DBMS.RANDOM_INITIALIZE 532 DO_WORK 220 OUTLN_PKG.DROP_BY_CAT 633 OUTLN_PKG.DROP_UNUSED 633 OUTLN_PKG.UPDATE_BY_CAT 633 SHOW_SPACE 300,345 SYS.DBMS_TTS 412 Процесс ARCH 200, 206, 229 ускорить работу 235 ARCn 124 DBWn 97, 98, 111 подчиненные процессы 133 производительность 128 ускорение работы 229 EMNn 132 ETL 399 LGWR 129, 198, 214 время работы 209 запись буфера журнала 582 исключительный доступ 234 ускорение работы 235 ora s000 ora816dev Р Разбор 73, 528, жесткий 48, 113, 553 мягкий 48, 553, 580 контроль процента 602 процент 597 Разделяемая блокировка чтения 170 побочные эффекты 171 Разделяемый пул 107, 117, 316, 528, 542 информация об использовании 581 использование 594 назначение 112 размер 114 очистка 587 сброс содержимого Предметный указатель Разделяемые блокировки чтения 171 побочный эффект 172 Размер транзакций 140 Раскрытие условий OR 639 Распределенная транзакция 127 сомнительная 127 Распространение блокировок 150 Расширенная поддержка очередей 123,131 Реестр 444 Режим 226, 546, 609 MTS 45, 83, 101, 546 использование 122 настройка и конфигурирование 120 ограничение максимальной степени параллелизма 122 правило номер один 120 принципиальное отличие 84 причины использования 122 NOLOGGING 226 особенность операций 226 архивирования журналов 124 выделенного сервера 45, 120 многопотокового сервера 45 оптимизации CHOOSE 609, 631 FIRST_ROWS 609, 612, 615 Резервное копирование 107, 133, 421 с помощью RMAN 107 Результирующее множество 65 Реорганизация данных 423 использование утилит EXP/IMP 423 Репликация 67 поддержка 131 схемы временный 90 дополнительный 287 кластеров 90 отката 90, 197, 56, 57, 241, 168, 169, 189 информация об использовании 590 использование 173, 190, 242, 245 необходимый размер 192 отключение 126 сжатие 126 таблицы 90 Сервер 83 выделенный 83, 120 разделяемый 83, 84, 120 приложений 70 Oracle iAS 70 Серверный процесс разделяемый 119 Сжатие ключей 390 Синоним 195 Система ООТ 98, СППР 98 информационно-поисковая 276 оперативной обработки транзакций 24, 45, 175 Системная глобальная область 80 Системное событие logoff 597 Системные вызовы 85, 119 fork() и ехес() 85 Скользящее среднее 280 Словарь данных 93, 99, 227, 422 защита 227 Службы 85 DNS 85 NetS 85 Oracle Names 85 TNS 85 репликации 197 События 582, 584 log file sync 582 SQL*Net message from client 584 существенные 584 базы данных 549 Совместное использование курсора 534, 638 Согласованность С Свойства ACID 179 Связи базы данных 195 Связываемая переменная 45, 47, 74, 466, 509 автоматическая подстановка 624, 637 автоматическое использование 72 влияние на производительность 528 использование 528, 536, 542, 556, 580, 591, 604 не использование 112, 208 ненужная 540 Сегмент 92 OVERFLOW Предметный указатель настройка 527 сравнение 139 Суррогатный ключ 319 Схема клонирования 398 Сценарий plustrce 33 utlxplan 32 cat7exp.sql 436 catblock.sql 161 connect.sql 32 demobld.sql 30 demodrop.sql 30 getallcode.sql 419 getallcodeJNSTALl 419 getallviews 420 gettrig.sql 421 login.sql 30, 31 spreport.sql 577 statsrep.sql по чтению 167 на уровне транзакции 174 Соединение 279 большого количества таблиц 325 Сообщение об ошибке 142, 192 ORA-00054 Resource Busy 142 ORA-01555 192 получение 192 Списки FREELIST 266 количества обращений 128 по давности использования 128 свободных мест 260, 262, 387, 529 индекс 387 недостаточное количество 262 Средства 519 контроля и отладки 573, 574 расширенной поддержки очередей 46 способ включения 574 Стабилизация плана оптимизатора 607 возможности 609 выполнение 619, 622 использование 611 Стандарт SQL92 62, 64, 170, 177 Степень параллелизма 122 максимально допустимой 122 Столбец NESTED_COLUMN_ID 379 NESTEDJABLEJD 316 OBJECTJYPE 350 SYS_NC_OID$ 332 SYS.NC_ROWINF$ 316 СУБД 138 Informix 139 Oracle 44, 8 1, 165, 308 фундаментальные отличия 165 SQL Server 183, 188, 194, 279 модель одновременного доступа 194 Sybase 61, 183, 188, 279 абстрактная схема 81 архитектура 44 понимание 44 знание 44 игнорирование особенностей 43 критика 43 идеальная T Таблица COLOCATED 354, 358 DEPT 147, 150, 161, 291, 294, 296, 379, 460, 478 DEPT.WORKING 472 DISORGANIZED 358 DUAL 110 EMP 147, 150, 161, 258, 277, 291, 296, 359, 364, 609 FAST.EMP 524 LOB_~DEMO 505 LOG 220 OBJ$ 126 OL$ 625 OLSHINTS 625 OUTLINE 619 OUTLN 642 SYS.OBJS 96 SMALL 252 STOP_OTHER_SESSION 252 UPPER.ENAME 278 X$BH 110 V$LOCK 154, 155, 158 в индексном кластере 290 в кластере 258 вложенная 258, 511 использование 314, 316, 321 семантическое отличие 313 синтаксис Предметный указатель хранение 318 временная 236, 258, 321, 544 атрибуты 324 добавление статистической информации 329 на время сеанса 322 на время транзакции 322 создание 324 в хеш-кластере 258, 298 объектная 258, 330 применять 331 применять операторы ЯМД 331 скалярные атрибуты 332 организованная в виде кучи 257, 271, 273 организованная по индексу 257, 276, 283, 319, 342 преимущества 276 использование пространства 283 проверочная 385 свойства 258 тип 257 транзакций 271 количество записей 275 первоначальный размер 271 фрагментированная 446 Табличное пространство 9 1, 92, 99, 302, 385, 410, 446 SYSTEM 90, 410, 590, 625, 639 USER 90 временное 93, 322 локально управляемое 93, 270 использование 302 переносимое 405, 410 использование 414 процедура проверки самодостаточности 411 режим READ ONLY 412 стандартное 446 управляемое по словарю 126 Терминология 259 Тестовый опрос 348 Тип данных 63 DATE 63 IDENTITY 64 ORDSYS.ORDIMAGE 506 SERIAL 64 TIME 63 объектные Точки сохранения 181 Транзакции 127 автономные 68 атрибуты 181 в среде MTS 45 неделимость 180 номер системного изменения 208 основное назначение 179 основное правило организации 202 перезапускаемые 194 плохие привычки при работе 188 продолжительность 188 размер 140, 188, 202, 207 правильный 234 распределенные 180, 194 выполнение 195 ограничения 196 сомнительные 195 только для чтения 175 уровня изолированности 165 частично завершенные 190 Трассировочный файл 547, 612 интерпретация 560 использование 560 стандартный заголовок 561 Требования 188 допускающее отложенную проверку 187 целостности 185 Триггер 60, 220, 224, 424, 612, 618, 623 AFTER 224 BEFORE 220, 224 INSTEAD OF 337, 424 для объектных представлений 337 ON LOGON 612, 618, 623 использование 618 Тщательный контроль доступа 71, 587 использование Удерживающий идентификатор 155 Управление по словарю 92 одновременным доступом 165 средства 165 табличным пространством 92 шаблонами 629 Управляющий файл 455, 459 для загрузки данных с разделителями 459 Уровень изолированности Предметный указатель READ COMMITTED 166 причина появления некорректных данных 169 READ UNCOMMITTED использование 166 REPEATABLE READ 170, 172, 175 SERIALIZABLE 43, 166, 173, 174 смысл 177 Установка 576 SQLJRACE 89, 525 TIMED_STATISTICS 89 Утилита 30, 4 5 1, 623, DBMSJJTILITY 550 DEBUG 575 EXP 397, 422, 431 использование различных версий 436 параметры 400 grep 560 IMP 397, 431 использование различных версий 436 особенности использования 397 параметры 403 SHOW 416 RMAN 115, 133 split 407 SQL*Plus 30, 85, 161, 182, 215, 397, 562, 601,623 побочный эффект использования 161 средство AUTOTRACE 215 SQLLDR 227, 451 вызов из хранимой процедуры 511 загрузка данных больших двоичных объектов 501 использование 455 использование функции 465 непосредственная загрузка 271 опции 517 параллельная загрузка данных 263 построение операторов INSERT 467 StatsPack 576 SVRMGRL 632 TKPROF 302, 390, 519, 552, 557, 602, 612, 613 использование 549, 551 опций командной строки 558 трассировочные файлы обрабатываются 560 импорта 626 экспорта Ф Файл CMAN.ORA 88 init.ora минимальный 88 неописанные параметры 89 LD 88 NAMES.ORA 88 oracle 125 PAR FILE 409 PROTOCOL. ORA 88 SQLNET.ORA 88 SORT_AREA_RETAINED.SIZE 101 TNSNAMES.ORA 85, 88 временный 87, 94 горячий 589 данных 87, 90, 91 временный 80, 94 параметров 80, 87, 88 параметров инициализации 134 паролей 87 журнала повторного выполнения 80, 87, 95 активный 206 архивный 206 размещение на неформатированных устройствах 236 сообщений 80 трассировочный 89, 146 управляющий 87, 95 Фиксация в цикле 190 время выполнения 207 каждой строки 219 продолжительность 208, 210 Функция CAST 63 DBMS_LOB.GETLENGTH 500 DBMSJ.OB.READ 501 DBMS_LOB.SUBSTR 488 DBMS_RANDOM.RANDOM 367 DUMP 348 MY_SOUNDEX 366 производительность 367 NVL2 149 SOUNDEX 366 SUBSTR 363, 368, 371 TO_CHAR 464 TO.DATE 372, 464 UPPER 365, Предметный указатель UTL_RAW.CAST_T0_VARCHAR2 501 Функциональный интерфейс JOBC 72, 194 OCI 72 ODBC 72, 194 для взаимодействия с базой данных 118 незнание особенностей 194 Функция DECODE 31 DUMP 444 free() 114 malloc() 114 SYS_GUID 333 SYS_NC.ROWINFO 332 SYS_OP_GUID 333 запросов 620 изменение категории 630 переименование 630 перенос 625 средства управления 629 удаление 631 Э w Экземпляр 80, 81 Экспериментирование 362 Экспорт непосредственный 428 Экспортирование в именованный канал 407 непосредственный режим 409 по частям 407 Экстент 92 временный 126 Эскалация блокирование 52, 150, 151 Эффективность работы экземпляра X Хеш-значение непреднамеренные совпадения 300 Хеш-кластер использование 302 количество ключей 308 однотабличный 306 создание 299 Хеш-таблица размер 299 Хеш-функция 298 специализированная 306 Хранилище данных 234, 308, 399 использование утилиты SOLLDR 452 метод индексирования 392 Хранимый шаблон 615 генерация 615 запросов 385 использование 644, 647 Хранимые процедуры 65 возвращающие результирующие множества 65 Целостность данных 166 Цель оптимизации FIRST_ROWS Я ЯМД 152 ЯОД Иностранные термины 2РС 195 ACID 179 Advanced Queues 46 AQ 46, 131 ARCH 206 ARCn 129 BSP 130 DATE 63 dbms.random.initialize 532 DBWn 128 DBWR 229 DELETE 138 EXP/IMP 487 FGAC 71 Go 53 Informix 138 inline view 149 interMedia 373, 506 Java-машин 115 Java-пул 107, 115 размер ц ч Чтение фантомов Ш Шаблон 610 влияние на производительность Предметный указатель SGA 80, 81, 122, 578 SNPn 131 SQL динамический 49, 62, 63 статический 62, 533 SQL* Loader 451 SQL*Plus приглашение 31 SQL-операторы рекурсивные 93 SQL92 64 SQLLDR 454 Sybase 138 TX 152 undo 197 UPDATE 138 V$ 210 V$MYSTAT 214 V$STATNAME LCKn 131 LGWR 125, 129, 209 LMD 130 LMON 130 MTS 114 NLS 442 OBJ$ 126 OCI 452 ODBC 194 OPS 151, 348 восстановление сбойного экземпляра 126 oracle 83 Oracle Forms 141, 147, 378, 548 генерирование оператора UPDATE 148 PCM 152 PMON 125 RECO 127 RMAN 395 roll forward 200 SCN 208, Аналитические функции SQL — очень мощный язык и лишь очень немногие запросы в нем нельзя создать. По опыту знаю, что можно придумать хитрый SQL-запрос для получения ответа практически на любой вопрос относительно любых данных. Однако производительность некоторых из этих запросов крайне низкая, да и придумать их непросто. Ряд запросов, которые сложно сформулировать на обычном языке SQL, весьма типичны: • Подсчет промежуточной суммы. Показать суммарную зарплату сотрудников отдела построчно, чтобы в каждой строке выдавалась сумма зарплат всех сотрудников вплоть до указанного. • Подсчет процентов в группе. Показать, какой процент от общей зарплаты по отделу составляет зарплата каждого сотрудника. Берем его зарплату и делим на сумму зарплат по отделу. • Запросы первых N. Найти N сотрудников с наибольшими зарплатами или N наиболее продаваемых товаров по регионам. • Подсчет скользящего среднего. Получить среднее значение по текущей и предыдущим N строкам. • Выполнение ранжирующих запросов. Показать относительный ранг зарплаты сотрудника среди других сотрудников того же отдела. Аналитические функции, появившиеся в версии Oracle 8.1.6, создавались для решения именно этих задач. Они расширяют язык SQL так, что подобные операции не только проще записываются, но и быстрее выполняются по сравнению с использованием чистого языка SQL. Эти расширения сейчас изучаются комитетом ANSI SQL с целью включения в спецификацию языка SQL.

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

Пример Простой пример подсчета промежуточной суммы зарплат по отделам с описанием того, что же в действительности происходит, позволят получить начальное представление о принципах использования аналитических функций: tkyte@TKYTE816> break on deptno skip 1 tkyte@TKYTE816> select ename, deptno, sal, 2 sum (sal) over 3 (order by deptno, ename) running_total, 4 sum(sal) over 5 (partition by deptno 6 order by ename) department_total, 7 row_number() over 8 (partition by deptno 9 order by ename) seq 10 from emp 11 order by deptno, ename 12 / ENAME CLARK KING MILLER ADAMS FORD JONES SCOTT SMITH ALLEN BLAKE JAMES MARTIN TURNER WARD 14 rows selected. В представленном выше коде удалось получить значение RUNNING_TOTAL для запроса в целом. Это было сделано по всему упорядоченному результирующему множеству с помощью конструкции SUM(SAL) OVER (ORDER BY DEPTNO, ENAME). Также удалось подсчитать промежуточные суммы по отделам, сбрасывая их в ноль при переходе к следующему отделу. Этого удалось добиться благодаря конструкции PARTITION BY DEPTNO 10 SAL RUNNING_TOTAL 2450 5000 1300 1100 3000 2975 3000 800 1600 2850 950 1250 1500 1250 2450 7450 8750 9850 12850 15825 18825 19625 21225 24075 25025 26275 27775 29025 DEPARTMENT_TOTAL 2450 7450 8750 1100 4100 7075 10075 10875 1600 4450 5400 6650 8150 9400 SEQ 1 2 3 1 2 3 4 5 1 2 3 4 5.

Аналитические функции DEPTNO в SUM(SAL) — в запросе была указана конструкция, задающая условие разбиения данных на группы. Для последовательной нумерации строк в каждой группе, в соответствии с критериями упорядочения, использовалась функция ROW_NUMBER() (для выдачи этого номера строки был добавлен столбец SEQ). В результате видно, что SCOTT — четвертый по списку сотрудник в отделе 20 при упорядочении по фамилии (ENAME). Функция ROW_NUMBER() используется и во многих других ситуациях, например для транспонирования или преобразования результирующих множеств (как будет описано далее). Этот новый набор функциональных возможностей содержит много замечательного. Он открывает абсолютно новые перспективы работы с данными. Можно избавиться от большого объема процедурного кода и сложных (или неэффективных) запросов, требующих много времени на разработку, и получить при этом желаемый результат. Чтобы почувствовать, насколько эффективными могут быть аналитические функции по сравнению с "чисто реляционными способами", давайте оценим производительность в случае 1000 строк, а не 14. При этом сравним производительность двух запросов: с новыми аналитическими функциями и на основе "старых" реляционных методов. Следующая пара операторов позволит создать аналог таблицы SCOTT.EMP с тремя столбцами — ENAME, DEPTNO и SAL — и индексом (единственным необходимым в данном примере). Я буду выбирать данные по столбцам DEPTNO и ENAME: tkyte@TKYTE816> c r e a t e t a b l e t 2 as 3 select object_name ename, 4 mod(object_id,50) deptno, 5 object_id sal 6 from all_objects 7 where rownum <= 1000 8/ Table c r e a t e d. tkyte@TKYTE816> c r e a t e index t_idx on t(deptno,ename);

Index c r e a t e d. Повторим запрос, но к новой таблице, задав установку AUTOTRACE TRACEONLY, чтобы увидеть, сколько и чего пришлось делать (для этого необходимо наличие роли PLUSTRACE): tkyte@TKYTE816> s e t a u t o t r a c e traceonly tkyte@TKYTE816> s e l e c t ename, deptno, s a l, 2 sum (sal) over 3 (order by deptno, ename) running_total, 4 sum(sal) over 5 (partition by deptno 6 order by ename) department_total, 7 row_number() over 8 (partition by deptno 9 order by ename) seq 10 from t emp 11 order by deptno, ename 1040 Глава 12 / 1000 rows selected. Elapsed: 00:00:00.61 Execution Plan 0 1 2 3 0 1 2 SELECT STATEMENT Optimizer=CHOOSE WINDOW (BUFFER) TABLE ACCESS (BY INDEX ROWID) OF 'T' INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE) Statistics 0 recursive calls 2 db block gets 292 consistent gets 66 physical reads 0 redo size 106978 bytes sent via SQL*Net to client 7750 bytes received via SQL*Net from client 68 SQL*Net roundtrips to/from client 0 sorts (memory) 1 sorts (disk) 1000 rows processed Итак, потребовалось 0,61 секунды и 294 логические операции ввода-вывода. Теперь выполним эквивалентный запрос, но используя только "стандартные" возможности языка SQL: tkyte@TKYTE816> s e l e c t ename, deptno, s a l, 2 (select sum(sal) 3 from t e2 4 where e2.deptno < emp.deptno 5 or (e2.deptno = emp.deptno and e2.ename <= emp.ename )) 6 running_total, 7 (select sum(sal) 8 from t e3 9 where e3.deptno = emp.deptno 10 and e3.ename <= emp.ename) 11 department_total, 12 (select count(ename) 13 from t e3 14 where e3.deptno = emp.deptno 15 and e3.ename <= emp.ename) 16 seq 17 from t emp 18 order by deptno, ename 19 / 1000 rows selected. Elapsed: 00:00:06. Аналитические функции Execution Plan 0 1 2 SELECT STATEMENT Optimizer=CHOOSE 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' 1 INDEX (PULL SCAN) OF 'T_IDX' (NON-UNIQUE) Statistics 0 0 665490 0 0 106978 7750 68 0 0 1000 recursive calls db block gets consistent gets physical reads redo size bytes sent via SQL*Net to client bytes received via SQL*Net from client SQL*Net roundtrips to/from client sorts (memory) sorts (disk) rows processed tkyte@TKYTE816> s e t autotrace off Оба запроса дали одинаковые результаты, но производительность отличается существенно. Время выполнения больше во много раз, а количество логических операций ввода-вывода увеличилось на несколько порядков. Аналитические функции обработали результирующее множество, использовав намного меньше ресурсов и, соответственно, быстрее. Более того, если рассмотреть синтаксис аналитических функций, оказывается, что записывать с их помощью запросы намного проще, чем на стандартном языке SQL. Чтобы почувствовать разницу, сравните текст двух представленных выше запросов.

Как работают аналитические функции В первой части этого раздела будут представлены подробности синтаксиса и определены термины. После этого мы перейдем непосредственно к примерам. Я продемонстрирую многие из 26 новых функций (не все, потому что при этом некоторые примеры повторялись бы). Аналитические функции используют общий синтаксис и предоставляют специфические возможности, создававшиеся для нужд технических дисциплин, незнакомых большинству разработчиков. Поняв принцип написания аналитических функций — как фрагментировать данные, как задавать окна данных и так далее, — использовать эти функции будет очень легко.

Синтаксис Синтаксис вызова аналитической функции на вид весьма прост, но эта простота может быть обманчивой. Все начинается с такой конструкции: ИМЯ_ФУНКЦИИ(<аргумент>,< аргумент >,... ) OVER (<конструкция_фрагментации> <конструкция_упорядочения> <конструкция_окна>) Глава Вызов аналитической функции может содержать до четырех частей: аргументы, конструкция фрагментации, конструкция упорядочения и конструкция, задающая окно. В представленном выше примере:

4 5 6 sum(sal) over (partition by deptno order by ename) department_total, • SUM — имя функции. • (SAL) — аргумент аналитической функции. Аналитические функции принимают от нуля до трех аргументов. В качестве аргументов передаются выражения, т.е. вполне можно было бы использовать SUM(SAL+COMM). • OVER — ключевое слово, идентифицирующее эту функцию как аналитическую. В противном случае синтаксический анализатор не мог бы отличить функцию агрегирования SUM() от аналитической функции SUM(). Конструкция после ключевого слова OVER описывает срез данных, "по которому" будет вычисляться аналитическая функция. • PARTITION BY DEPTNO — необязательная конструкция фрагментации. Если конструкция фрагментации не задана, все результирующее множество считается одним большим фрагментом. Это используется для разбиения результирующего множества на группы, так что аналитическая функция применяется к группам, а не ко всему результирующему множеству. В первом примере главы, когда конструкция фрагментации не указывалась, функция SUM по столбцу SAL вычислялась для всего результирующего множества. Фрагментируя результирующее множество по столбцу DEPTNO, мы вычисляли SUM по столбцу SAL для каждого отдела (DEPTNO), сбрасывая промежуточную сумму для каждой группы. • ORDER BY ENAME — необязательная конструкция ORDER BY;

для некоторых функций она обязательна, для других — нет. Функции, зависящие от упорядочения данных, например LAG и LEAD, которые позволяют обратиться к предыдущим и следующим строкам в результирующем множестве, требуют обязательного указания конструкции ORDER BY. Другие функции, например AVG, не требуют. Эта конструкция обязательна, если используется любая функция работы с окном (подробнее см. далее в разделе "Конструкция окна"). Конструкция ORDER BY определяет, как упорядочиваются данные в группах при вычислении аналитической функции. В нашем случае упорядочивать по DEPTNO и ENAME не нужно, потому что по столбцу DEPTNO выполнялась фрагментация, т.е. неявно предполагается, что столбцы, по которым выполняется фрагментация, по определению входят в ключ сортировки (конструкция ORDER BY применяется к каждому фрагменту поочередно). • Конструкция окна в данном примере отсутствует. Именно ее синтаксис иногда кажется сложным. Подробно возможные способы задания конструкции окна будут рассмотрены ниже. Теперь более детально рассмотрим каждую из четырех частей вызова аналитической функции, чтобы понять, как их можно задавать.

Аналитические функции Функции Сервер Oracle предлагает 26 аналитических функций. Они разбиваются на четыре основных класса по возможностям. Первый класс образуют различные функции ранжирования, позволяющие строить запросы типа "первых N". Мы уже использовали одну функцию этого класса, ROW_NUMBER, при генерации столбца SEQ в предыдущем примере. Она ранжировала сотрудников в отделах по фамилии (ENAME). Точно так же их можно было бы ранжировать по зарплате (SALARY) или любому другому атрибуту. Второй класс образуют оконные функции, позволяющие вычислять разнообразные агрегаты. В первом примере этой главы была показана такая функция — мы вычисляли SUM(SAL) по разным группам. Вместо функции SUM можно было использовать и другие функции агрегирования, например COUNT, AVG, MIN, МАХ и т.д. К третьему классу относятся различные итоговые функции. Они очень похожи на оконные, поэтому имеют те же имена: SUM, MIN, MAX и т.д. Тогда как оконные функции используются для работы с окнами данных, как промежуточная сумма в предыдущем примере, итоговые функции работают со всеми строками фрагмента или группы. Например, если бы в первоначальном запросе использовались обращения: sum(sal) over () t o t a l _ s a l a r y, sum(sal) over ( p a r t i t i o n by deptno) total_salary_for_department мы бы получили общие суммы по группам, а не промежуточные. Ключевое отличие итоговой функции от оконной — отсутствие конструкции ORDER BY в операторе OVER. При отсутствии конструкции ORDER BY функция применяется к каждой строке группы. При наличии конструкции ORDER BY функция применяется к окну (подробнее об этом в разделе, описывающем конструкцию окна). Есть также функции LAG и LEAD, позволяющие получать значения из предыдущих или следующих строк результирующего множества. Это помогает избежать самосоединения данных. Например, если в таблице записаны даты визитов пациентов к врачу и необходимо вычислить время между визитами для каждого их них, очень пригодится функция LAG. Можно просто фрагментировать данные по пациентам и отсортировать их по дате. После этого функция LAG легко сможет вернуть данные предыдущей записи для пациента. Останется вычесть из одной даты другую. До появления аналитических функций для получения этих данных приходилось организовывать сложное соединение таблицы с ней же самой. Наконец, есть большой класс статистических функций, таких как VAR_POP, VAR_SAMP, STDEV_POP, набор функций линейной регрессии и т.п. Эти функции позволяют вычислять значения статистических показателей для любого неупорядоченного фрагмента. В конце раздела, посвященного синтаксису, представлена таблица с кратким объяснением назначения всех аналитических функций.

Конструкция фрагментации Конструкция PARTITION BY логически разбивает результирующее множество на N групп по критериям, задаваемым выражениями фрагментации. Слова "фрагмент" и "груп Глава па" в этой главе и в документации Oracle используются как синонимы. Аналитические функции применяются к каждой группе независимо, — для каждой новой группы они сбрасываются. Например, ранее при демонстрации функции, вычисляющей промежуточную сумму зарплат, фрагментация выполнялась по столбцу DEPTNO. Когда значение в столбце DEPTNO в результирующем множестве изменялось, происходил сброс промежуточной суммы в ноль, и суммирование начиналось заново. Если не указать конструкцию фрагментации, все результирующее множество считается одной группой. Во первом примере мы использовали функцию SUM(SAL) без конструкции фрагментации, чтобы получить промежуточные суммы для всего результирующего множества. Интересно отметить, что каждая аналитическая функция в запросе может иметь уникальную конструкцию фрагментации;

фактически уже в простейшем примере в начале главы это и было сделано. Для столбца RUNNING_TOTAL конструкция фрагментации не была задана, поэтому целевой группой было все результирующее множество. Для столбца DEPARTMENTAL_TOTAL результирующее множество фрагментируется по отделам, что позволило вычислять промежуточные суммы для каждого из них. Синтаксис конструкции фрагментации прост и очень похож на синтаксис конструкции GROUP BY в обычных SQL-запросах: PARTITION BY выражение [, выражение] [, выражение] Конструкция упорядочения Конструкция ORDER BY задает критерий сортировки данных в каждой группе (в каждом фрагменте). Это, несомненно, влияет на результат выполнения любой аналитической функции. При наличии (или отсутствии) конструкции ORDER BY аналитические функции вычисляются по-другому. В качестве примера рассмотрим, что происходит при использовании функции AVG() с конструкцией ORDER BY и без оной: scott@TKYTE816> select ename, sal, avg(sal) over () 2 from emp;

3/ ENAME SMITH ALLEN WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS JAMES FORD MILLER SAL AVG(SAL)OVER() 800.00 1600.00 1250.00 2975.00 1250.00 2850.00 2450.00 3000.00 5000.00 1500.00 1100.00 950.00 3000.00 1300.00 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073.21 2073. 14 rows selected.

Аналитические функции scott@TKYTE816> select ename, sal, avg(sal) over (ORDER BY ENAME) 2 from emp 3 order by ename 4/ ENAME ADAMS ALLEN BLAKE CLARK FORD JAMES JONES KING MARTIN MILLER SCOTT SMITH TURNER WARD SAL AVG(SAL)OVER(ORDERBYENAME) 1100.00 1600.00 2850.00 2450.00 3000.00 950.00 2975.00 5000.00 1250.00 1300.00 3000.00 800.00 1500.00 1250.00 1100.00 1350.00 1850.00 2000.00 2200.00 1991.67 2132.14 2490.63 2352.78 2247.50 2315.91 2189.58 2136.54 2073. 14 rows selected. В отсутствие конструкции ORDER BY среднее значение вычисляется по всей группе, и одно и то же значение выдается для каждой строки (функция используется как итоговая). Когда функция AVG() используется с конструкцией ORDER BY, среднее значение в каждой строке является средним по текущей и всем предыдущим строкам (функция используется как оконная). Например, средняя зарплата для пользователя ALLEN в результатах выполнения запроса с конструкцией ORDER BY — 1350 (среднее для значений 1100 и 1600). Немного забегая вперед, в следующий раздел, посвященный конструкции окна, можно сказать, что наличие конструкции ORDER BY в вызове аналитической функции добавляет стандартную конструкцию окна — RANGE UNBOUNDED PRECEDING. Это означает, что для вычисления используется набор из всех предыдущих и текущей строки в текущем фрагменте. При отсутствии ORDER BY стандартным окном является весь фрагмент. Чтобы реально почувствовать, как все это работает, рекомендую применить одну и ту же аналитическую функцию при двух различных конструкциях ORDER BY. В первом примере текущая сумма вычисляется для всей таблицы ЕМР с использованием конструкции ORDER BY DEPTNO, ENAME. При этом текущая сумма вычисляется для всех строк, причем, порядок их просмотра определяется конструкцией ORDER BY. Если изменить порядок указания столбцов в этой конструкции на противоположный или вообще сортировать по другим столбцам, получаемые текущие суммы будут существенно отличаться;

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

Глава ops$tkyte@DEV816> select ename, deptno, 2 sum(sal) over (order by ename, deptno) sum_ename_deptno, 3 sum(sal) over (order by deptno, ename) sum_deptno_ename 4 from emp 5 order by ename, deptno 6/ ENAME ADAMS ALLEN BLAKE CLARK FORD JAMES JONES KING MARTIN MILLER SCOTT SMITH TURNER WARD 14 rows selected. Оба столбца SUM(SAL) одинаково корректны;

один из них содержит SUM(SAL) при упорядочении по столбцу DEPTNO, а потом — по ENAME, а другой — при упорядочении по столбцу ENAME, а потом — по DEPTNO. Поскольку результирующее множество упорядочено по (ENAME, DEPTNO), значения SUM(SAL), вычислявшиеся именно в этом порядке, кажутся более корректными, но общая сумма совпадает: 29025. Конструкция ORDER BY в аналитических функциях имеет следующий синтаксис:

ORDER BY в ы р а ж е н и е [ASC | DESC] [NULLS FIRST | NULLS LAST] DEPTNO SUM_ENAME_DEPTNO SUM_DEPTNO_ENAME 20 30 30 10 20 30 20 10 30 10 20 20 30 30 1100 2700 5550 8000 11000 11950 14925 19925 21175 22475 25475 26275 27775 29025 9850 21225 24075 2450 12850 25025 15825 7450 26275 8750 18825 19625 27775 Она совпадает с конструкцией ORDER BY для запроса, но будет упорядочивать строки только в пределах фрагментов и может не совпадать с конструкцией ORDER BY для запроса в целом (или любого другого фрагмента). Конструкции NULLS FIRST и NULLS LAST впервые появились в версии Oracle 8.1.6. Они позволяют указать, где при упорядочении должны быть значения NULL — в начале или в конце. В случае сортировки по убыванию (DESC), особенно в аналитических функциях, эта новая возможность принципиально важна. Почему — описано в разделе "Проблемы" в конце главы.

Конструкция окна Синтаксис этой конструкции на первый взгляд кажется сложным из-за используемых ключевых слов. Конструкция вида RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, задающая стандартное окно при использовании конструкции ORDER BY, не похожа на те, что постоянно используются разработчиками. Синтаксис конструкции окна достаточно сложен для описания. Вместо попыток перерисовать синтаксические схемы, представленные в руководстве Oracle8i SQL Reference Manual, я перечислю все варианты конструкции окна и опишу, какой набор данных в пределах груп Аналитические функции пы задает соответствующий вариант. Сначала, однако, давайте разберемся, что вообще позволяет сделать конструкция окна. Конструкция окна позволяет задать перемещающееся или жестко привязанное окно (набор) данных в пределах группы, с которым будет работать аналитическая функция. Например, конструкция диапазона RANGE UNBOUNDED PRECEDING означает: "применять аналитическую функцию к каждой строке данной группы, с первой по текущую". Стандартным является жестко привязанное окно, начинающееся с первой строки группы и продолжающееся до текущей. Если используется следующая аналитическая функция: SUM(sal) OVER (PARTITION BY d e p t n o ORDER BY ename ROWS 2 PRECEDING) d e p a r t m e n t _ t o t a l 2, то будет создано перемещающееся окно в группе, и сумма зарплат будет вычисляться по столбцу SAL текущей и двух предыдущих строк в этой группе. Если необходимо создать отчет, показывающий сумму зарплат текущего и двух предыдущих сотрудников отдела, соответствующий сценарий может выглядеть так: scott@TKYTE816> break on deptno scott@TKYTE816> s e l e c t deptno, ename, s a l, 2 sum(sal) over 3 (partition by deptno 4 order by ename 5 rows 2 preceding) sliding_total 6 from emp 7 order by deptno, ename 8/ DEPTNO ENAME 10 CLARK KING MILLER 20 ADAMS FORD JONES SCOTT SMITH 30 ALLEN BLAKE JAMES MARTIN TURNER WARD 14 rows selected. SAL SLIDING_TOTAL 2450 5000 1300 1100 3000 2975 3000 800 1600 2850 950 1250 1500 1250 2450 7450 8750 1100 4100 7075 8975 6775 1600 4450 5400 5050 3700 Нас интересует эта часть запроса:

2 3 sum(sal) over (partition by deptno 1048 Глава 4 5 order by ename rows 2 preceding) sliding_total Конструкция, определяющая фрагментацию, приводит к вычислению SUM(SAL) по отделам, независимо от других групп (значение SUM(SAL) сбрасывается при изменении номера отдела). Конструкция ORDER BY ENAME приводит к сортировке данных в каждом отделе по столбцу ENAME;

это позволяет с помощью конструкции окна, rows 2 preceding, при суммировании зарплат обращаться к двум предыдущим строкам в соответствии с заданным порядком сортировки. Например, значение в столбце SLIDING_TOTAL для сотрудника SMITH — 6775, что равно сумме значений 800, 3000 и 2975. Это сумма зарплат в строке для SMITH и двух предыдущих строках окна. Можно создавать окна по двум критериям: по диапазону (RANGE) значений данных или по смещению (ROWS) относительно текущей строки. Конструкция RANGE уже встречалась ранее, RANGE UNBOUNDED PRECEDING например. Она требует брать все строки вплоть до текущей, в соответствии с порядком, задаваемым конструкцией ORDER BY. Следует помнить, что для использования окон необходимо задавать конструкцию ORDER BY. Сейчас мы рассмотрим задание окон с помощью конструкций ROWS и RANGE, а затем другие способы задания окон.

Окна диапазона Окна диапазона объединяют строки в соответствии с заданным порядком. Если в запросе сказано, например, "range 5 preceding", то будет сгенерировано перемещающееся окно, включающее предыдущие строки группы, отстоящие от текущей строки не более чем на 5 строк. Диапазон можно задавать в виде числового выражения или выражения, значением которого является дата. Применять конструкцию RANGE с другими типами данных нельзя. Если имеется таблица ЕМР со столбцом HIREDATE типа даты и задана аналитическая функция count(*) over (order by hiredate asc range 100 preceding) она найдет все предыдущие строки фрагмента, значение которых в столбце HIREDATE лежит в пределах 100 дней от значения HIREDATE текущей строки. В этом случае, поскольку данные сортируются по возрастанию (ASC), значения в окне будут включать все строки текущей группы, у которых значение в столбце HIREDATE меньше значения HIREDATE текущей строки, но не более чем на 100 дней. Если использовать функцию count(*) over (order by hiredate desc range 100 preceding) и сортировать фрагмент по убыванию (DESC), базовая логика работы останется той же, но, поскольку группа отсортирована иначе, в окно попадет другой набор строк. В рассматриваемом случае функция найдет все строки, предшествующие текущей, где значение в поле HIREDATE больше значения HIREDATE в текущей строке, но не более чем на 100 дней. Пример поможет это прояснить. Я буду использовать запрос с аналитической функцией FIRST_VALUE. Эта функция возвращает вычисленное значение для первой строки окна. Так мы легко сможем понять, где начинается окно:

Аналитические функции scott@TKYTE816>-select ename, sal, hiredate, hiredate-100 windowtop, 2 first_value(ename) 3 over (order by hiredate asc 4 range 100 preceding) ename_prec, 5 first_value(hiredate) 6 over (order by hiredate asc 7 range 100 preceding) hiredate_prec 8 from emp 9 order by hiredate asc 10 / ENAME SMITH ALLEN WARD JONES BLAKE CLARK TURNER MARTIN KING FORD JAMES MILLER SCOTT ADAMS 14 rows selected. SAL HIREDATE 800 1600 1250 2975 2850 2450 1500 1250 5000 3000 950 1300 3000 1100 17-DEC-80 20-FEB-81 22-FEB-81 02-APR-81 01-MAY-81 09-JUN-81 08-SEP-81 28-SEP-81 17-NOV-81 03-DEC-81 03-DEC-81 23-JAN-82 09-DEC-82 12-JAN-83 WINDOW_TOP ENAME_PREC HIREDATE_ 08-SEP-80 12-NOV-80 14-NOV-80 23-DEC-80 21-JAN-81 01-MAR-81 31-MAY-81 20-JUN-81 09-AUG-81 25-AUG-81 25-AUG-81 15-OCT-81 31-AUG-82 04-OCT-82 SMITH SMITH SMITH ALLEN ALLEN JONES CLARK TURNER TURNER TURNER TURNER KING SCOTT SCOTT 17-DEC-80 17-DEC-80 17-DEC-80 20-FEB-81 20-FEB-81 02-APR-81 09-JUN-81 08-SEP-81 08-SEP-81 08-SEP-81 08-SEP-81 17-NOV-81 09-DEC-82 09-DEC- Мы упорядочили один фрагмент по критерию HIREDATE ASC. При этом использовалась аналитическая функция FIRST_VALUE для поиска первого значения ENAME и первого значения HIREDATE в соответствующем окне. Посмотрев на строку данных для сотрудника CLARK, можно обнаружить, что для него значение в столбце HIREDATE — 9 июня 1981 года, 09-JUN-81, а дата за 100 дней до этой соответствует 1 марта 1981 года, 01-MAR-81. Для удобства эта дата помещена в столбец WINDOWTOP. Аналитическая функция затем вычисляется для всех строк отсортированного фрагмента, предшествующих строке для сотрудника CLARK и имеющих значение в столбце HIREDATE в диапазоне с 01-MAR-81 по 09-JUN-81. Первое значение ENAME для этого окна — JONES. Это имя и выдает аналитическая функция в столбце ENAME_PREC. Упорядочив данные по критерию HIREDATE DESC, мы получим: scott@TKYTE816> s e l e c t ename, s a l, h i r e d a t e, hiredate+100 windowtop, 2 first_value(ename) 3 over (order by hiredate desc 4 range 100 preceding) ename_prec, 5 first_value(hiredate) 6 over (order by hiredate desc 7 range 100 preceding) hiredate_prec 8 from emp 9 order by hiredate desc 10 / 1050 Глава ENAME ADAMS SCOTT MILLER FORD JAMES KING MARTIN TURNER CLARK BLAKE JONES WARD ALLEN SMITH SAL HIREDATE 1100 3000 1300 3000 950 5000 1250 1500 2450 2850 2975 1250 1600 800 12-JAN-83 09-DEC-82 23-JAN-82 03-DEC-81 03-DEC-81 17-NOV-81 28-SEP-81 08-SEP-81 09-JUN-81 01-MAY-81 02-APR-81 22-FEB-81 20-FEB-81 17-DEC-80 WINDOWTOP ENAME_PREC HIREDATE_ 22-APR-83 19-MAR-83 03-MAY-82 13-MAR-82 13-MAR-82 25-FEB-82 06-JAN-82 17-DEC-81 17-SEP-81 09-AUG-81 ll-JUL-81 02-JUN-81 31-MAY-81 27-MAR-81 ADAMS ADAMS MILLER MILLER MILLER MILLER FORD FORD TURNER CLARK CLARK BLAKE BLAKE WARD 12-JAN-83 12-JAN-83 23-JAN-82 23-JAN-82 23-JAN-82 23-JAN-82 03-DEC-81 03-DEC-81 08-SEP-81 09-JUN-81 09-JUN-81 01-МАУ-81 01-MAY-81 22-FEB- 14 rows selected. Если снова обратиться к строке сотрудника CLARK, окажется, что выбрано другое окно, поскольку данные фрагмента отсортированы по-иному. Окно для строки CLARK по условию RANGE 100 PRECEDING теперь доходит до строки TURNER, поскольку значение HIREDATE для TURNER — последняя дата среди значений HIREDATE в строках, предшествующих строке CLARK, отличающаяся не более чем на 100 дней. Иногда достаточно сложно понять, какие значения будут входить в диапазон. Я считаю использование функции FIRST_VALUE удобным методом, помогающим увидеть диапазоны окна и проверить, корректно ли установлены параметры. Теперь, представив диапазоны окон, мы используем их для вычисления чего-то более существенного. Пусть необходимо выбрать зарплату каждого сотрудника и среднюю зарплату всех принятых на работу в течение 100 предыдущих дней, а также среднюю зарплату всех принятых на работу в течение 100 следующих дней. Соответствующий запрос будет выглядеть так: scott@TKYTE816> select ename, hiredate, sal, 2 avg(sal) 3 over (order by hiredate asc range 100 preceding) 4 avg_sal_100_days_before, 5 avg(sal) 6 over (order by hiredate desc range 100 preceding) 7 avg_sal_100_days_after 8 from emp 9 order by 8/ ENAME SMITH ALLEN WARD JONES BLAKE CLARK TURNER HIREDATE 17-DEC-80 20-FEB-81 22-FEB-81 02-APR-81 01-MAY-81 09-JUN-81 08-SEP-81 SAL AVG_SAL_100_DAYS_BEPORE AVG_SAL_100_DAYS_AFTER 800.00 1600.00 1250.00 2975.00 2850.00 2450.00 1500.00 800.00 1200.00 1216.67 1941.67 2168.75 2758.33 1975.00 1216.67 2168.75 2358.33 2758.33 2650.00 1975.00 2340. Аналитические функции 2550.00 2562.50 1750.00 1750.00 1300.00 2050.00 1100. MARTIN KING JAMES FORD MILLER SCOTT ADAMS 28-SEP-81 17-NOV-81 03-DEC-81 03-DEC-81 23-JAN-82 09-DEC-82 12-JAN- 1250.00 5000.00 950.00 3000.00 1300.00 3000.00 1100. 1375.00 2583.33 2340.00 2340.00 2562.50 3000.00 2050. 14 rows selected. Если теперь снова обратиться к строке для сотрудника CLARK, то, поскольку мы уже понимаем, какое окно в группе будет с ней связано, легко убедиться, что средняя зарплата (2758,33) равна (2450+2850+2975)/3. Это средняя зарплата для строки CLARK и строк, предшествующих CLARK (это строки для сотрудников JONES и BLAKE) при упорядочении данных по возрастанию. С другой стороны, средняя зарплата 1975,00 равна (2450+1500)/2. Это средняя зарплата для строки CLARK и строк, предшествующих CLARK при упорядочении данных по убыванию. С помощью этого запроса можно одновременно вычислить среднюю зарплату для сотрудников, принятых на работу за 100 дней до и за 100 дней после сотрудника CLARK. Окна диапазона можно задавать только по данным типа NUMBER или DATE, поскольку нельзя добавить или вычесть N единиц из значения типа VARCHAR2. Еще одно ограничение для таких окон состоит в том, что в конструкции ORDER BY может быть только один столбец — диапазоны по природе своей одномерны. Нельзя задать диапазон в N-мерном пространстве.

Окна строк Окна срок задаются в физических единицах, строках. Перепишем вступительный пример из предыдущего раздела, задав окно строк: count (*) over (order by x R W 5 preceding) OS Это окно будет включать до 6 строк: текущую и пять предыдущих (порядок определяется конструкцией ORDER BY). Для окон по строкам нет ограничений, присущих окнам по диапазону;

данные могут быть любого типа и упорядочивать можно по любому количеству столбцов. Вот пример, сходный с рассмотренным ранее: scott@TKYTE816> select ename, sal, hiredate, 2 first_value(ename) 3 over (order by hiredate asc 4 rows 5 preceding) ename_prec, 5 first_value(hiredate) 6 over (order by hiredate asc 7 rows 5 preceding) hiredate_prec 8 from emp 9 order by hiredate asc 10 / ENAME SMITH ALLEN WARD SAL HIREDATE ENAME_PREC HIREDATE_ 800.00 17-DEC-80 SMITH 1600.00 20-FEB-81 SMITH 1250.00 22-EEB-81 SMITH 17-DEC-80 17-DEC-80 17-DEC- Глава JONES 2975.00 BLAKE 2850.00 CLARK 2450.00 TURNER 1500.00 MARTIN 1250.00 KING 5000.00 JAMES 950.00 FORD 3000.00 MILLER 1300.00 SCOTT 3000.00 ADAMS 1100.00 14 rows selected.

02-APR-81 01-MAY-81 09-JUN-81 08-SEP-81 28-SEP-81 17-NOV-81 03-DEC-81 03-DEC-81 23-JAN-82 09-DEC-82 12-JAN- SMITH SMITH SMITH ALLEN WARD JONES BLAKE CLARK TURNER MARTIN KING 17-DEC-80 17-DEC-80 17-DEC-80 20-FEB-81 22-FEB-81 02-APR-81 01-MAY-81 09-JUN-81 08-SEP-81 28-SEP-81 17-NOV- Взглянув на строку для сотрудника CLARK, можно увидеть, что первой в окне ROWS 5 PRECEDING следует строка для сотрудника SMITH — она просто пятая перед строкой для CLARK в соответствии с заданным порядком. Строка для сотрудника SMITH будет первой в окне и для всех предыдущих строк (для BLAKE, JONES и т.д.). Дело в том, что строка для SMITH — первая в данной группе (она будет первой и для самой себя). При сортировке группы по возрастанию окна изменяются: scott@TKYTE816> select ename, sal, hiredate, 2 first_value(ename) 3 over (order by hiredate desc 4 rows 5 preceding) ename_prec, 5 first_value(hiredate) 6 over (order by hiredate desc 7 rows 5 preceding) hiredate_prec 8 from emp 9 order by hiredate desc 10 / ENAME ADAMS SCOTT MILLER JAMES FORD KING MARTIN TURNER CLARK BLAKE JONES WARD ALLEN SMITH SAL HIREDATE 1100.00 3000.00 1300.00 950.00 3000.00 5000.00 1250.00 1500.00 2450.00 2850.00 2975.00 1250.00 1600.00 800.00 12-JAN-83 09-DEC-82 23-JAN-82 03-DEC-81 03-DEC-81 17-NOV-81 28-SEP-81 08-SEP-81 09-JUN-81 01-MAY-81 02-APR-81 22-FEB-81 20-FEB-81 17-DEC-80 ENAME_PREC HIREDATE_ ADAMS ADAMS ADAMS ADAMS ADAMS ADAMS SCOTT MILLER JAMES FORD KING MARTIN TURNER CLARK 12-JAN-83 12-JAN-83 12-JAN-83 12-JAN-83 12-JAN-83 12-JAN-83 09-DEC-82 23-JAN-82 03-DEC-81 03-DEC-81 17-NOV-81 28-SEP-81 08-SEP-81 09-JUN- 14 rows selected. Теперь первое значение для набора из 5 строк, предшествующих в группе строке для сотрудника CLARK, — строка для сотрудника JAMES. Теперь можно вычислить сред Аналитические функции нюю зарплату для указанного сотрудника и пяти принятых на работу до него и после него: scott@TKYTE816> select ename, hiredate, sal, 2 avg(sal) 3 over (order by hiredate asc rows 5 preceding) avg_5_before, 4 count(*) 5 over (order by hiredate asc rows 5 preceding) obs_before, 6 avg(sal) 7 over (order by hiredate desc rows 5 preceding) avg_5_after, 8 count(*) 9 over (order by hiredate desc rows 5 preceding) obs_after 10 from emp 11 order by hiredate 12 / ENAME SMITH ALLEN WARD JONES BLAKE CLARK TURNER MARTIN KING JAMES FORD MILLER SCOTT ADAMS HIREDATE 17-DEC-80 20-FEB-81 22-FEB-81 02-APR-81 01-MAY-81 09-JUN-81 08-SEP-81 28-SEP-81 17-NOV-81 03-DEC-81 03-DEC-81 23-JAN-82 09-DEC-82 12-JAN-83 SAL AVG_5_BEFORE OBS_BEPOPE AVG_5_APTER OBS_AFTER 800.00 1600.00 1250.00 2975.00 2850.00 2450.00 1500.00 1250.00 5000.00 950.00 3000.00 1300.00 3000.00 1100.00 800.00 1200.00 1216.67 1656.25 1895.00 1987.50 2104.17 2045.83 2670.83 2333.33 2358.33 2166.67 2416.67 2391.67 1.00 2.00 3.00 4.00 5.00 6.00 6.00 6.00 6.00 6.00 6.00 6.00 6.00 6.00 1987.50 2104.17 2045.83 2670.83 2675.00 2358.33 2166.67 2416.67 2391.67 1587.50 1870.00 1800.00 2050.00 1100.00 6.00 6.00 6.00 6.00 6.00 6.00 6.00 6.00 6.00 4.00 5.00 3.00 2.00 1. 14 rows selected. Обратите внимание, что я выбирал также значение COUNT(*). Это позволяет понять, по какому количеству строк было вычислено среднее значение. Можно явно увидеть, что для вычисления средней зарплаты сотрудников, принятых до сотрудника ALLEN, использовалось только 2 записи, а для вычисления средней зарплаты сотрудников, нанятых после него, — 6. В том месте группы, где находится строка для сотрудника ALLEN, есть только 1 предыдущая запись, а при вычислении аналитической функции используются все имеющиеся строки.

Задание окон Теперь, понимая различие между окнами диапазонов и окнами строк, можно изучать способы задания окон. В простейшем случае, окно задается с помощью одной из трех следующих взаимоисключающих конструкций. • UNBOUNDED PRECEDING. Окно начинается с первой строки текущей группы и заканчивается текущей обрабатываемой строкой.

1054 Глава • CURRENT ROW. Окно начинается (и заканчивается) текущей строкой. • Числовое_выражение PRECEDING. Окно начинается со строки за числовое_выражение строк до текущей, если оно задается по строкам, или со строки, меньшей по значению столбца, упомянутого в конструкции ORDER BY, не более чем на числовое выражение, если оно задается по диапазону. Окно CURRENT ROW в простейшем виде, вероятно, никогда не используется, поскольку ограничивает применение аналитической функции одной текущей строкой, а для этого аналитические функции не нужны. В более сложном случае для окна задается также конструкция BETWEEN. В ней CURRENT ROW можно указывать в качестве начальной или конечной строки окна. Начальную и конечную строку окна в конструкции BETWEEN можно задавать с использованием любой из перечисленных выше конструкций и еще одной, дополнительной: • Числовое_выражение FOLLOWING. Окно заканчивается (или начинается) со строки, через числовое_выражение строк после текущей, если оно задается по строкам, или со строки, большей по значению столбца, упомянутого в конструкции ORDER BY, не более чем на числовое_выражение, если оно задается по диапазону. Рассмотрим ряд примеров такого задания окон: scott@TKYTE816> select deptno, ename, hiredate, 2 count(*) over (partition by deptno 3 order by hiredate nulls first 4 range 100 preceding) cnt_range, 5 count(*) over (partition by deptno 6 order by hiredate nulls first 7 rows 2 preceding) cnt_rows 8 from emp 9 where deptno in (10, 20) 10 order by deptno, hiredate 11 / DEPTNO ENAME 10 CLARK KING MILLER 20 SMITH JONES FORD SCOTT ADAMS 8 rows selected. Как видите, окно RANGE 100 PRECEDING содержит только строки текущего фрагмента, предшествующие текущей строке, и те, значение которых HIREDATE находится в диапазоне HIREDATE-100 и HIREDATE относительно текущей. В данном случае таких строк всегда 1 или 2, т.е. интервал между приемом людей на работу обычно преHIREDATE 09-JUN-81 17-NOV-81 23-JAN-82 17-DEC-80 02-APR-81 03-DEC-81 09-DEC-82 12-JAN-83 CNT_RANGE 1 1 2 1 1 1 1 2 CNT_ROWS 1 2 3 1 2 3 3 Аналитические функции вышает 100 дней (за исключением двух случаев). Окно ROWS 2 PRECEDING, однако, содержит от 1 до 3 строк (это определяется тем, как далеко текущая строка находится от начала группы). Для первой строки группы имеем значение 1 (предыдущих строк нет). Для следующей строки в группе таких строк 2. Наконец, для третьей и далее строк значение COUNT(*) остается постоянным, поскольку мы считаем только текущую строку и две предыдущие. Теперь рассмотрим использование конструкции BETWEEN. Все заданные до сих пор окна заканчивались текущей строкой и возвращались по результирующему множеству в поисках дополнительной информации. Можно задать окно так, что обрабатываемая строка не будет последней, а окажется где-то в середине окна. Например: scott@TKYTE816> select ename, hiredate, 2 first_value(ename) over 3 (order by hiredate asc 4 range between 100 preceding and 100 following), 5 last_value(ename) over 6 (order by hiredate asc 7 range between 100 preceding and 100 following) 8 from emp 9 order by hiredate asc 10 / ENAME SMITH ALLEN WARD JONES BLAKE CLARK TURNER MARTIN KING FORD JAMES MILLER SCOTT ADAMS HIREDATE FIRST_VALU LAST_VALUE 17-DEC-80 20-FEB-81 22-FEB-81 02-APR-81 01-MAY-81 09-JUN-81 08-SEP-81 28-SEP-81 17-NOV-81 03-DEC-81 03-DEC-81 23-JAN-82 09-DEC-82 12-JAN-83 SMITH SMITH SMITH ALLEN ALLEN JONES CLARK TURNER TURNER TURNER TURNER KING SCOTT SCOTT WARD BLAKE BLAKE CLARK CLARK TURNER JAMES JAMES MILLER MILLER MILLER MILLER ADAMS ADAMS 14 rows selected. Обратившись снова к строке для сотрудника CLARK, можно убедиться, что окно начинается со строки для JONES и продолжается до строки для сотрудника TURNER. Теперь в окно входят строки для тех, кто принят на работу за 100 дней до и (а не или, как прежде) после текущего сотрудника. Итак, теперь мы хорошо знаем синтаксис четырех компонентов вызова аналитической функции. Это: • • имя функции;

конструкция фрагментации, используемая для разбиения результирующего множества на независимые группы;

Глава • конструкция ORDER BY, сортирующая данные в группе для оконных функций;

• конструкция окна, задающая набор строк, к которым применяется аналитическая функция. ИМЯ_ФУНКЦИИ(<аргумент>, <аргумент>,... ) OVER (конструкция фрагментации> <конструкция упорядочений <конструкция окна>) Рассмотрим кратко предлагаемые функции.

Функции Сервер предлагает более 26 аналитических функций. Имена некоторых из них совпадают с именами функций агрегирования, например AVG и SUM. Другие, с новыми именами, обеспечивают новые возможности. В этом разделе будут перечислены имеющиеся функции и кратко описано их назначение. Аналитическая функция AVG([DISTINCT | ALL] выражение) Назначение Используется для вычисления среднего значения выражения в пределах группы и окна. Для поиска среднего после удаления дублирующихся значений можно указывать ключевое слово DISTINCT, Выдает коэффициент корреляции для пары выражений, возвращающих числовые значения. Это сокращение для выражения: COVAR_POP<выражение1, выражение2) / STDDEV_POP(выражение!.) * STDDEV_POP(выражение2)). В статистическом смысле, корреляция — это степень связи между переменными. Связь между переменными означает, что значение одной переменной можно в определенной степени предсказать по значению другой. Коэффициент корреляции представляет степень корреляции в виде числа в диапазоне от -1 (высокая обратная корреляция) до 1 (высокая корреляция). Значение 0 соответствует отсутствию корреляции, COUNT( [DISTINCT] [*] [выражение]) Эта функция считает строки в группах. Если указать * или любую константу, кроме NULL, функция count будет считать все строки. Если указать выражение, функция count будет считать строки, для которых выражение имеет значение не NULL. Можно задавать модификатор DISTINCT, чтобы считать строки в группах после удаления дублирующихся строк.

CORR (выражение, выражение) COV AR_POP( выражение, Возвращает ковариацию генеральной совокупности выражение) (population covariance) пары выражений с числовыми значениями. COVAR_SAMP Возвращает выборочную ковариацию (sample covariance) (выражение, выражение) пары выражений с числовыми значениями.

Аналитические функции Аналитическая функция Назначение CUME_DIST Вычисляет относительную позицию строки в группе. Функция CUME_DIST всегда возвращает число большее О и меньше или равное 1. Это число представляет "позицию" строки в группе из N арок. В группе из трех строк, например, возвращаются следующие значения кумулятивного распределения: 1/3, 2/3 и 3/3. Эта функция вычисляет относительный ранг каждой возвращаемой запросом строки по отношению к другим строкам, основываясь на значениях выражений в конструкции ORDER BY. Данные в группе сортируются в соответствии с конструкцией ORDER BY, а затем каждой строке поочередно присваивается числовой ранг, начиная с 1. Ранг увеличивается при каждом изменении значений выражений, входящих в конструкцию ORDER BY. Строки с одинаковыми значениями получают один и тот же ранг (при этом сравнении значения NULL считаются одинаковыми). Возвращаемый этой функцией "плотный" ранг дает ранговые значения без промежутков. Сравните с представленной далее функцией RANK. Возвращает первое значение в группе.

DENSE_RANK FIRST_VALUE LAG(выражение, Функция LAG дает доступ к другим строкам <смещение>, результирующего множества, избавляя от необходимости <стандартное значение>) выполнять самосоединения. Она позволяет работать с курсором как с массивом. Можно ссылаться на строки, предшествующие текущей строке в группе. О том, как обращаться к следующим строкам в группе, см. в описании функции LEAD. Смещение - это положительное целое число со стандартным значением 1 (предыдущая строка). Стандартное значение возвращается, если индекс выходит за пределы окна (для первой строки группы будет возвращено стандартное значение). LAST_VALUE Возвращает последнее значение в группе. LEAD(выpaжeниe, Функция LEAD противоположна функции LAG. Если <смещение>, функция LAG дает доступ к предшествующим строкам <стандартное значение>) группы, то функция LEAD позволяет обращаться к строкам, следующим за текущей. Смещение — это положительное целое число со стандартным значением 1 (следующая строка). Стандартное значение возвращается, если индекс выходит за пределы окна (для последней строки группы будет возвращено стандартное значение). МАХ(выражение) МIN(выражение) Находит максимальное значение выражения в пределах окна в группе. Находит минимальное значение выражения в пределах окна в группе.

Глава Аналитическая функция Назначение NTILE( выражение) Делит группу на фрагменты по значению выражения. Например, если выражение = 4, то каждой строке в группе присваивается число от 1 до 4 в соответствии с фрагментом, з которую она попадает. Если в группе 20 строк, первые 5 получат значение 1, следующие 5 — значение 2 и т.д. Если количество строк в группе не делится на значение выражения без остатка, строки распределяются так, что ни в одном фрагменте количество строк не превосходит минимальное количество в других фрагментах более чем на 1, причем дополнительные строки будут в группах с меньшими номера фрагмента. Например, если снова выражение = 4, а количество строк = 21, в первом фрагменте будет 6 строк, во втором и последующих - 5. Аналогична функции CUME_DIST (кумулятивное распределение). Вычисляет ранг строки в группе минус 1, деленный на количество обрабатываемых строк минус 1. Эта функция всегда возвращает значения в диапазоне от 0 до 1 включительно. Эта функция вычисляет относительный ранг каждой строки, возвращаемой запросом, на основе значений выражений, входящих в конструкцию ORDER BY. Данные в группе сортируются в соответствии с конструкцией ORDER BY, а затем каждой строке поочередно присваивается числовой ранг, начиная с 1. Строки с одинаковыми значениями выражений, входящих в конструкцию ORDER BY, получают одинаковый ранг, но если две строки получат одинаковый ранг, следующее значение ранга пропускается. Если две строки получили ранг 1, строки с рангом 2 не будет;

следующая строка в группе получит ранг 3. В этом отличие от функции DENSE_RANK, которая не пропускает значений. Эта функция вычисляет значение выражение / (sum(выражение)) по строкам группы. Это дает процент, который составляет значение текущей строки по отношению к sum(выражение) REGR_xxxxxxx Эти функции линейной регрессии применяют стандартную (выражение, выражение) линейную регрессию по методу наименьших квадратов к паре выражений. Предлагается 9 различных функций регрессии. ROW NUMBER Возвращает смещение строки по отношению к началу упорядоченной группы. Может использоваться для последовательной нумерации строк, упорядоченных по определенным критериям.

Вычисляет стандартное (среднеквадратичное) отклонение PERCENT RANK RANK RATIO_TO_REPORT (выражение) STDDEV(выражение) (standard deviation) текущей строки по отношению к группе.

Аналитические функции Аналитическая функция Назначение STDDEV_POP Эта функция вычисляет стандартное отклонение (выражение) генеральной совокупности (population standard deviation) и возвращает квадратный корень из дисперсии генеральной совокупности (population variance). Она возвращает значение, совпадающее с квадратным корнем из результата функции VAR_POP, Эта функция вычисляет накопленное стандартное отклонение выборки (cumulative sample standard deviation) и возвращает квадратный корень выборочной дисперсии (sample variance). Она возвращает значение, совпадающее с квадратным корнем из результата функции VAR_SAMP, Вычисляет общую сумму значений выражения для группы. Эта функция возвращает дисперсию генеральной совокупности для набора числовых значений (значения NULL игнорируются). Функция VAR_POP вычисляет значение: (SUM(выражение* выражение) — SUM(выражение)*SUM(выражение) / COUNT(выражение)) / COUNT(выражение) STDDEV_SAMP (выражение) SUM(выражение) VAR_TOP(выражение) VAR_SAMP(выражение) Эта функция возвращает выборочную дисперсию для набора числовых значений (значения NULL игнорируются). Она вычисляет значение: (SUM(выражение*выражение) — SUM(выражение)*SUM(выражение) / COUNT(выражение)) / (COUNT(выражение) - 1) VARIANCE(выpaжeниe) Возвращает дисперсию для выражения. Сервер Oracle вычисляет дисперсию как: • 0, если количество строк в группе = 1;

• VAR_SAMP, если количество строк в группе > 1.

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

Запрос первых N Мне часто задают вопрос: "Как получить первых N записей набора полей?". До появления аналитических функций ответить на такой вопрос было очень трудно. С запросами первых N записей, однако, бывают трудности связанные в основном с формулировкой задачи. Это надо учитывать при проектировании отчетов. Рассмотрим следующее, вполне разумное на первый взгляд требование: получить для каждого отдела трех наиболее высокооплачиваемых специалистов по продажам. Однако эта задача неоднозначна из-за возможного повторения значений: в отделе может быть четыре человека с одинаково огромной зарплатой, и что тогда делать?

1060 Глава Я могу предложить как минимум три одинаково разумных интерпретации этого требования, причем каждой интерпретации может соответствовать и не три записи! Требование можно интерпретировать так. • Выдать список специалистов по продажам, имеющих одну из трех максимальных зарплат. Другими словами, найти все различные значения зарплаты, отсортировать, выбрать три наибольших, и вернуть всех сотрудников, зарплата которых совпадает с одним из этих трех значений. • Выдать до трех человек с максимальными зарплатами. Если четыре человека имеют одинаковую максимальную зарплату, в ответ не должно выдаваться ни одной строки. Если два сотрудника имеют максимальную зарплату и два — следующую по значению, ответ будет предполагать две строки (два сотрудника с максимальной зарплатой). • Отсортировать специалистов по продажам по убыванию зарплат. Вернуть первые три строки. Если в отделе менее трех специалистов по продажам, в результате будет менее трех записей. После дополнительных вопросов и уточнений оказывается, что некоторым необходима первая интерпретация;

другим — вторая или третья. Давайте рассмотрим, как с помощью аналитических функций сформулировать все три запроса, и как это делалось без них. Для этих примеров используем таблицу SCOTT.EMP. Сначала реализуем запрос "Выдать список специалистов по продажам в каждом отделе, имеющих одну из трех максимальных зарплат": scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 dense_rank() 4 over (partition by deptno 5 order by sal desc) 6 dr from emp) 7 where dr <= 3 8 order by deptno, sal desc 9/ DEPTNO ENAME 10 KING CLARK MILLER 20 SCOTT FORD JONES ADAMS 30 BLAKE ALLEN TURNER 10 rows selected. SAL 5000 2450 1300 3000 3000 2975 1100 2850 1600 1500 DR 1 2 3 1 1 2 3 1 2 Аналитические функции Здесь для получения трех максимальных зарплат была использована функция DENSE_RANK(). Мы присвоили записям непрерывные ранговые значения по столбцу sal и отсортировали результат по убыванию. Если обратиться к описанию функций, оказывается, что при непрерывном ранжировании значения ранга не пропускаются и две строки с одинаковыми значениями получают одинаковый ранг. Поэтому после построения результирующего множества в виде подставляемого представления, можно просто выбирать все строки с "плотным" рангом не более трех. В результате для каждого отдела будут получены все сотрудники с одной из трех максимальных зарплат в отделе. Для сравнения выберем функцию RANK и сравним, что происходит при обнаружении дублирующихся значений: scott@TKYTE816> select deptno, ename, sal, 2 dense_rank() 3 over (partition by deptno 4 order by sal desc) dr, 5 rank() 6 over (partition by deptno 7 order by sal desc) r 8 from emp 9 order by deptno, sal desc 10 / DEPTNO ENAME 10 KING CLARK MILLER 20 SCOTT FORD JONES ADAMS SMITH 30 BLAKE ALLEN TURNER WARD MARTIN JAMES 14 rows selected. Если бы использовалась функция RANK, сотрудник ADAMS (получивший ранг 4) не вошел бы в результирующее множество, но он — один из сотрудников отдела 20, получивших одну из трех максимальных зарплат, так что в результат он попадать должен. В данном случае использование функции RANK вместо DENSE_RANK привело бы к неправильному ответу на поставленный вопрос. Наконец, пришлось использовать подставляемое представление и задать псевдоним DR для результатов аналитической функции dense_rank(). Дело в том, что нельзя использовать аналитические функции в конструкциях WHERE или HAVING непосредственно, так что пришлось выбрать результат в представление, а затем отфильтровать, SAL 5000 2450 1300 3000 3000 2975 1100 800 2850 1600 1500 1250 1250 950 DR 1 2 3 1 1 2 3 4 1 2 3 4 4 5 R 1 2 3 1 1 3 4 5 1 2 3 4 4 1062 Глава оставив только необходимые строки. Использование подставляемого представления с условием — типичная конструкция для многих примеров в этой главе. Теперь вернемся к запросу "Выдать не более трех человек с максимальными зарплатами по каждому отделу": scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 count(*) over (partition by deptno 4 order by sal desc 5 range unbounded preceding) 6 cnt from emp) 7 where cnt <= 3 8 order by deptno, sal desc 9/ DEPTNO ENAME 10 KING CLARK MILLER 20 SCOTT FORD JONES 30 BLAKE ALLEN TURNER 9 rows selected. Этот запрос немного нетривиален. Мы подсчитываем все записи в окне, предшествующие текущей, при сортировке по зарплате. Диапазон RANGE UNBOUNDED PRECEDING задает окно, включающее все записи, зарплата в которых больше или равна зарплате в текущей записи, поскольку сортировка выполнена по убыванию (DESC). Подсчитывая всех сотрудников с такой же или более высокой зарплатой, можно выбирать только строки, в которых значение этого количества (CNT), меньше или равно 3. Обратите внимание, что в отделе 20 для сотрудников SCOTT и FORD возвращается значение 2. Оба они получили наибольшую зарплату в отделе, так что попадают в окно друг для друга. Интересно отметить небольшое отличие, которое дает следующий запрос: scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 count(*) over (partition by deptno 4 order by sal desc, ename 5 range unbounded preceding) 6 cnt from emp) 7 where cnt <= 3 8 order by deptno, sal desc 9/ DEPTNO ENAME 10 KING SAL 5000 CNT 1 SAL 5000 2450 1300 3000 3000 2975 2850 1600 1500 CNT 1 2 3 2 2 3 1 2 Аналитические функции CLARK MILLER 20 FORD SCOTT JONES 30 BLAKE ALLEN TURNER 9 rows selected.

2450 1300 3000 3000 2975 2850 1600 2 3 1 2 3 1 2 Обратите внимание, как добавление столбца в конструкцию ORDER BY повлияло на окно. Ранее сотрудники FORD и SCOTT оба имели в столбце CNT значение 2. Причина в том, что окно строилось исключительно по столбцу зарплаты. Более избирательное окно дает другие результаты функции COUNT. Я привел этот пример, чтобы подчеркнуть, что функция окна зависит от обеих конструкций, ORDER BY и RANGE. Если фрагмент сортировался только по зарплате, строка для сотрудника FORD предшествовала строке для SCOTT, когда строка для SCOTT была текущей, а строка для SCOTT, в свою очередь, предшествовала строке для FORD, когда та была текущей. Только при сортировке по столбцам SAL и ENAME можно однозначно упорядочить строки для сотрудников SCOTT и FORD по отношению друг к другу. Чтобы убедиться, что этот подход, предусматривающий использование функции COUNT, позволяет возвращать не более трех записей, давайте изменим данные так, чтобы максимальная зарплата была у большего числа сотрудников отдела: scott@TKYTE816> update emp set sal = 99 where deptno = 30;

б rows updated. scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 count(*) over (partition by deptno 4 order by sal desc 5 range unbounded preceding) 6 cnt from emp) 7 where cnt <= 3 8 order by deptno, sal desc 9/ DEPTNO ENAME 10 KING CLARK MILLER 20 SCOTT FORD JONES 6 rows selected. Теперь строк для отдела 30 в отчете нет, поскольку 6 сотрудников этого отдела имеют одинаковую зарплату. В поле CNT для всех них находится значение 6, которое никак не меньше или равно 3. SAL 5000 2450 1300 3000 3000 2975 CNT 1 2 3 2 2 1064 Глава Перейдем теперь к последнему запросу: "Отсортировать специалистов по продажам по убыванию зарплат и вернуть первые три строки". Это легко сделать с помощью функции ROW_NUMBER(): scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 row_number() over (partition by deptno 4 order by sal desc) 5 rn from emp) 6 where rn <= 3 7/ DEPTNO ENAME 10 KING CLARK MILLER 20 SCOTT FORD JONES 30 ALLEN BLAKE MARTIN 9 rows selected. При выполнении запроса каждый фрагмент сортируется по убыванию значений зарплат, после чего по мере обработки каждой строке фрагмента присваивается последовательный номер. После этого с помощью конструкции WHERE мы получаем только первые три строки каждого фрагмента. В примере с транспонированием результирующего множества мы используем такой же прием для преобразования строк в столбцы. Следует отметить, однако, что для отдела DEPTNO=30 возвращаются в определенном смысле случайные строки. Если помните, информация в отделе 30 была изменена так, что все 6 сотрудников получили значение 99 в столбце зарплаты. Можно в некоторой степени управлять тем, какие три записи будут возвращаться, с помощью конструкции ORDER BY. Например, можно использовать конструкцию ORDER BY SAL DESC, ENAME для получения упорядоченной по фамилии информации о наиболее высокооплачиваемых сотрудниках, если несколько из них имеют одинаковую зарплату. Интересно отметить, что с помощью функции ROW_NUMBER можно получать произвольную секцию данных из группы строк. Это может пригодиться в среде, не поддерживающей информацию о состоянии, когда надо выдавать данные постранично. Например, если необходимо выдавать данные из таблицы ЕМР, отсортированные по столбцу ENAME, группами по пять строк, можно использовать запрос следующего вида: scott@TKYTE816> select ename, hiredate, sal 2 from (select ename, hiredate, sal, 3 row_number() over (order by ename) 4 rn from emp) 5 where rn between 5 and 10 6 order by rn 7/ SAL 5000 2450 1300 3000 3000 2975 99 99 99 RN 1 2 3 1 2 3 1 2 Аналитические функции ENAME FORD JAMES JONES KING MARTIN MILLER HIREDATE 03-DEC-81 03-DEC-81 02-APR-81 17-NOV-81 28-SEP-81 23-JAN- SAL 3000 950 2975 5000 1250 6 rows selected. И напоследок, чтобы продемонстрировать всю мощь аналитических функций, сравним запросы с аналитическими функциями с такими же запросами, где эти функции не используются. Для сравнения я создал таблицу Т, являющуюся увеличенной во всех смыслах разновидностью таблицы ЕМР: scott@TKYTE816> create table t 2 as 3 select object_name ename, 4 mod(object_id,50) deptno, 5 object_id sal 6 from all_objects 7 where rownum <= 1000 8/ Table created. scott@TKYTE816> create index t_idx on t(deptno,sal desc);

Index created. scotteTKYTE816> analyze table t 2 compute statistics 3 for table 4 for all indexed columns 5 for all indexes 6/ Table analyzed. Мы создали индекс по этой таблице, позволяющий ответить на запросы, которые мы будем к ней выполнять. Теперь сравним тексты и производительность запросов с аналитическими функциями и без них. Для сравнения производительности я использовал установки SQL_TRACE, TIMED_STATISTICS и утилиту TKPROF. Подробнее об этих средствах и интерпретации результатов см. в главе 10, посвященной стратегиям и средствам настройки производительности: scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 dense_rank() over (partition by deptno 4 order by sal desc) 5 dr from t) 6 where dr <= 3 7 order by deptno, sal desc 8/ 1066 Глава call Parse Execute Fetch total count 1 2 11 14 cpu 0.00 0.00 0.01 0.01 elapsed 0.00 0.00 0.07 0.07 disk 0 0 7 7 query 0 0 10 10 current 0 0 17 17 rows 0 0 150 Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 54 Rows Row Source Operation 150 VIEW 364 WINDOW SORT PUSHED RANK 1000 TABLE ACCESS FULL T ************************************ scott@TKYTE816> select deptno, ename, sal 2 from t el 3 where sal in (select sal 4 from (select distinct sal, deptno 5 from t e3 6 order by deptno, sal desc) e2 7 where e2.deptno = e1.deptno 8 and rownum <= 3) 9 order by deptno, sal desc 10 / call Parse Execute Fetch total count 1 1 11 13 cpu 0.00 0.00 0.80 0.80 elapsed 0.00 0.00 0.80 0.80 disk 0 0 0 0 query 0 0 10010 10010 current 0 0 12012 12012 rows 0 0 150 Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 54 Rows Row Source Operation 150 SORT ORDER BY 150 FILTER 1001 TABLE ACCESS FULL T 1000 FILTER 3700 COUNT STOPKEY 2850 VIEW 2850 SORT ORDER BY STOPKEY 20654 SORT UNIQUE 20654 TABLE ACCESS FULL T Аналитические функции Оба представленных выше запроса возвращают данные о трех сотрудниках, которые получают наибольшие зарплаты. Запрос, использующий аналитические функции, позволяет получить эти сведения без особых усилий: для этого требуется 0,01 секунды процессорного времени и 27 логических операций ввода-вывода. А реляционный запрос требует выполнения более 22000 логических операций ввода-вывода, и на это уходит 0,80 секунды процессорного времени. Для выполнения запроса, не использующего аналитические функции, необходимо выполнять подзапрос для каждой строки в таблице Т, чтобы найти три наибольших зарплаты в данном отделе. Этот запрос не только медленнее выполняется, но и сложен для написания. Его производительность можно повысить с помощью подсказок, но при этом он стал бы еще менее понятным и более "хрупким". (Как любой запрос, использующий подсказки: подсказка — это всего лишь предложение, и оптимизатор может его проигнорировать). Запрос с аналитическими функциями, определенно, выигрывает как по производительности, так и по простоте. Теперь переходим ко второму вопросу: выдать до трех человек с максимальными зарплатами. scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 count(*) over (partition by deptno 4 order by sal desc 5 range unbounded preceding) 6 cnt from t) 7 where cnt <= 3 8 order by deptno, sal desc 9/ call Parse Execute Fetch total count 1 2 11 14 cpu 0.01 0.00 0.02 0.03 elapsed 0.01 0.00 0.12 0.13 disk 0 0 15 15 query 0 0 10 10 current 0 0 17 17 rows 0 0 150 Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 54 Rows Row Source Operation 150 VIEW 1000 WINDOW SORT 1000 TABLE ACCESS FULL T ************************************* scott@TKYTE816> select deptno, ename, sal 2 from t el 3 where (select count(*) 4 from t e2 5 where e2.deptno = e1.deptno 6 and e2.sal >= e1.sal) <= 1068 Глава 7 order by deptno, sal desc 8/ call Parse Execute Fetch total count 1 1 11 13 cpu 0.01 0.00 0.60 0.61 elapsed 0.01 0.00 0.66 0.67 disk 0 0 0 0 query 0 0 4010 4010 current 0 0 4012 4012 rows 0 0 150 Misses In library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 54 Rows Row Source Operation 150 SORT ORDER BY 150 FILTER 1001 TABLE ACCESS FULL T 2000 SORT AGGREGATE 10827 INDEX FAST FULL SCAN (object id 27867) И на этот раз результаты говорят сами за себя: 0,03 секунды процессорного времени и 27 логических операций ввода-вывода по сравнению с 0,61 секунды процессорного времени и более чем 8000 логических операций ввода-вывода. Снова явное преимущество имеет запрос, использующий аналитические функции. Причина и на этот раз в том, что при отсутствии аналитических функций для каждой строки базовой таблицы приходится выполнять коррелированный подзапрос. Этот подзапрос подсчитывает количество записей в том же отделе для сотрудников с такой же или более высокой зарплатой. Выбираются только записи, для которых это количество меньше или равно 3. Результаты запросов одинаковы, но используемые во время выполнения ресурсы принципиально различны. Составить оба запроса одинаково несложно, но производительность при использовании аналитических функций несравнимо выше. Наконец, необходимо получить для каждого отдела первых три сотрудника с наибольшими зарплатами. Результаты следующие: scott@TKYTE816> select deptno, ename, sal 2 from t el 3 where (select count(*) 4 from t e2 5 where e2.deptno = el.deptno 6 and e2.sal >= el.sal ) <=3 7 order by deptno, sal desc 8/ call Parse Execute Fetch total count 1 2 11 14 cpu 0.00 0.00 0.00 0.00 elapsed 0.00 0.00 0.12 0.12 disk 0 0 14 14 query 0 0 10 10 current 0 0 17 17 rows 0 0 150 Аналитические функции Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 54 Rows Row Source Operation 150 VIEW 1000 WINDOW SORT 1000 TABLE ACCESS FULL T ************************************* scott@TKYTE816> select deptno, ename, sal 2 from t el 3 where (select count(*) 4 from t e2 5 where e2.deptno = e1.deptno 6 and e2.sal >= e1.sal 7 and (e2.sal > e1.sal OR e2.rowid > e1.rowid)) < 3 8 order by deptno, sal desc 9/ call Parse Execute Fetch total count 1 1 11 13 cpu 0.00 0.00 0.88 0.88 elapsed 0.00 0.00 0.88 0.88 disk 0 0 0 0 query 0 0 4010 4010 current 0 0 4012 4012 rows 0 0 150 Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 54 Rows 150 150 1001 2000 9827 Row Source Operation SORT ORDER BY FILTER TABLE ACCESS FULL T SORT AGGREGATE INDEX FAST FULL SCAN (object id 27867) И на этот раз производительность запросов несравнима. Версия с аналитической функцией во много раз превосходит по производительности реляционный запрос. Кроме того, версию с использованием аналитических функций в данном случае написать намного проще. Созданный коррелированный подзапрос весьма сложен. Необходимо подсчитать количество сотрудников в отделе, у которых зарплата больше или равна зарплате в текущей записи. Кроме того, если зарплата не больше зарплаты в текущей записи (совпадает с ней), такую запись можно учитывать, только если значение ROWID (или любого столбца с уникальными значениями) больше, чем в текущей записи. Это гарантирует, что строки не будут считаться дважды, а каждый раз будет выбираться другой набор строк.

Глава Во всех случаях оказалось, что аналитические функции не только упрощают написание сложных запросов, но и могут существенно повысить производительность. Они позволяют делать то, что не стоит запрашивать с помощью "чистого" языка SQL из-за неэффективности выполнения.

Запрос с транспонированием При запросе с транспонированием (опорный запрос — pivot query) берутся данные вида: C al al al C C xl x2 x b1 b1 b и выдаются в следующем виде:

Cl a C bl C3(l) xl C3(2) x C3(3) x Этот запрос преобразует строки в столбцы. Например, можно выдать должности сотрудников отдела в виде столбцов: DEPTNO JOB_1 10 CLERK 20 ANALYST 30 CLERK а не в виде строк: DEPTNO JOB 10 CLERK 10 MANAGER 10 PRESIDENT 20 ANALYST 20 CLERK 20 MANAGER 30 CLERK 30 MANAGER 30 SALESMAN Я представлю два примера запросов с транспонированием. Первый — разновидность описанного выше запроса трех сотрудников с максимальными зарплатами. Второй пример показывает, как транспонировать любое результирующее множество, и дает шаблон необходимых для этого действий. Предположим, необходимо выдать фамилии сотрудников отдела с тремя наибольшими зарплатами в виде столбцов. Запрос должен возвращать ровно одну строку для каждого отдела, причем в строке должно быть 4 столбца: номер отдела (DEPTNO), фамилия сотрудника с наибольшей зарплатой в отделе, фамилия сотрудника со следующей JOB_2 MANAGER ANALYST MANAGER JOB_3 PRESIDENT CLERK SALESMAN Аналитические функции по величине зарплатой и т.д. С помощью новых аналитических функций это сделать просто (а до их появления — практически невозможно): ops$tkyte@DEV816> select deptno, 2 max(decode(seg,1,ename,null)) highest_paid, 3 max(decode(seq,2,ename,null)) second_highest, 4 max(decode(seq,3,ename,null)) third_highest 5 from (SELECT deptno, ename, 6 row_number() OVER 7 (PARTITION BY deptno 8 ORDER BY sal desc NULLS LAST) seg 9 FROM emp) 10 where seg <= 3 11 group by deptno 12 / DEPTNO HIGHEST_PA SECOND_HIG THIRD_HIGH 10 KING 20 SCOTT 30 BLAKE CLARK FORD ALLEN MILLER JONES TURNER Мы создали внутреннее результирующее множество, где сотрудники отделов пронумерованы по убыванию зарплат. Функция decode во внешнем запросе оставляет только строки со значениями номеров 1, 2 или 3 и присваивает взятые из них фамилии соответствующему столбцу. Конструкция GROUP BY позволяет избавиться от лишних строк и получить сжатый результат. Возможно, понять, что я имею в виду, проще, если сначала посмотреть на результирующее множество запроса без конструкций GROUP BY и МАХ: scott@TKYTE816> select deptno, 2 decode(seq,1,ename,null) highest_paid, 3 decode(seq,2,ename,null) second_highest, 4 decode(seq, 3,ename,null) third_highest 5 from (select deptno, ename, 6 row_number() over 7 (partition by deptno 8 order by sal desc nulls last) 9 seq from emp) 10 where seq <= 3 11 / DEPTNO HIGHEST_PA SECOND_HIG THIRD_HIGH 10 KING 10 10 20 SCOTT 20 20 30 ALLEN CLARK MILLER FORD JONES BLAKE 1072 Глава 30 MARTIN 9 rows selected. Функция агрегирования МАХ будет применяться конструкцией группировки GROUP BY по столбцу DEPTNO. Значение в столбце HIGHEST_PAID для отдела только в одной строке будет непустым — в остальных строках этот столбец всегда будет иметь значение NULL. Функция МАХ будет выбирать только строку с непустым значением. Поэтому сочетание группирования и функции МАХ позволит, убрав значения NULL, "свернуть" результирующее множество и получить желаемый результат. Если есть таблица Т со столбцами С1 и С2 и необходимо получить результат вида: Cl C2(l) C2(2).... C2(N), где столбец С1 должен присутствовать во всех строках (значения выдаются по направлению к концу страницы), а столбец С2 должен быть транспонирован так, чтобы он представлялся в виде строк (значения С2 выдаются по направлению к концу строки, они становятся столбцами, а не строками), надо создать такой запрос: select cl, mах(decode(rn,1,с2,null)) c2_l, max(decode(rn,2,c2,null)) c2_2, max(decode(rn,N,c2,null)) c2_N from (select cl, c2, row_number() over (partition by Cl order by <столбцы>) rn from T <условие>) group by Cl В представленном выше примере в качестве С1 использовался столбец DEPTNO, a в качестве С2 — ENAME. Поскольку упорядочение выполнялось по критерию SAL DESC, первые три полученные строки соответствовали трем наиболее высокооплачиваемым сотрудникам соответствующего отдела (напоминаю: если максимальные зарплаты получало четыре человека, одного из них мы теряем). Второй пример: транспонировать результирующее множество. Рассмотрим более общий случай, когда опорный (отсюда и второе название запроса — опорный) столбец, С1, и транспонируемый столбец, С2, представляют собой наборы столбцов. Решение очень похоже на то, что представлено выше. Предположим, необходимо для каждого отдела и должности выдать фамилии и зарплаты сотрудников. При этом в отчете фамилии и соответствующие зарплаты должны выдаваться в строке, как столбцы. Кроме того, в строке сотрудников надо упорядочивать слева направо по возрастанию зарплат. Для решения этой проблемы необходимо выполнить следующее: scott@TKYTE816> select max(count(*)) from emp group by deptno, job;

MAX(COUNT(*)) 4 В результате мы получаем количество столбцов. Теперь можно создавать запрос:

Аналитические функции scott@TKYTE816> select deptno, job, 2 max(decode(rn, 1, ename, null)) ename_l, 3 max(decode(rn, 1, sal, null)) sal_l, 4 max(decode(rn, 2, ename, null)) ename_2, 5 max(decode(rn, 2, sal, null)) sal_2, 6 max(decode(rn, 3, ename, null)) ename_3, 7 max(decode(rn, 3, sal, null)) sal_3, 8 max(decode(rn, 4, ename, null)) ename_4, 9 max(decode(rn, 4, sal, null)) sal_4 10 from (select deptno, job, ename, sal, 11 row_number() over (partition by deptno, job 12 order by sal, ename) 13 rn from emp) 14 group by deptno, job 15 / DEPTNO JOB ENAME_1SAL_1 ENAME_2 SAL_2 ENAME_3 SAL_3 ENAME_ SAL_4 1300 2450 5000 3000 SCOTT 800 ADAMS 2975 99 99 99 MARTIN 3000 10 CLERK MILLER 10 MANAGER CLARK 10 PRESIDENT KING 20 ANALYST 20 CLERK 20 MANAGER 30 CLERK 30 MANAGER 30 SALESMAN 9 rows selected. FORD SMITH JONES JAMES BLAKE ALLEN 99 TURNER 99 WARD Ранее в этой главе мы установили значение зарплаты 99 для сотрудников отдела 30. Для транспонирования произвольного результирующего множества можно пойти еще дальше. Если имеется набор столбцов С1, С2, СЗ,... CN и значения столбцов С1... Сх должны выдаваться во всех строках, а значения столбцов Сх+1... CN — в виде столбцов каждой строки, запрос будет иметь такой синтаксис: Select Cl, C2,... CX, max(decode(rn,l,C{X+l},null)) cx+l_l,...rnax(decode(rn,l,CN,null)) CN_1 max(decode(rn,2,C{X+l},null)) cx+l_2,...max(decode(rn,l,CN,null)) CN_2 max(decode(rn,N,c{X+l},null)) cx+l_N,...max(decode(rn,l,CN,null)) CN_N from (select Cl, C2,... CN row_number() over (partition by Cl, C2,... CX order by <столбцы>) rn from T <условие>) group by Cl, C2,... CX В предыдущем примере в качестве С1 использовался столбец DEPTNO, в качестве С2 — JOB, СЗ представлял столбец ENAME, а С4 — SAL. Для создания подобного запроса надо знать максимальное количество строк, которое может быть в фрагменте. Оно определяет количество генерируемых столбцов. В SQL 1074 Глава необходимо знать количество выбираемых столбцов, т.к. в противном случае мы не сможем транспонировать результирующее множество. Таким образом, можно привести еще более общий пример создания запроса с транспонированием. Если заранее, до выполнения, общее количество столбцов не известно, придется использовать динамический SQL, чтобы справиться с переменным списком выбора в операторе SELECT. Для демонстрации этого можно написать PL/SQL-процедуру;

в результате мы получим универсальную процедуру для транспонирования любого результирующего множества. Эта процедура (я поместил ее в пакет) будет иметь следующую спецификацию: scott@TKYTE816> create or replace package my_pkg 2 as 3 type refcursor is ref cursor;

4 type array is table of varchar2(30);

5 procedure pivot(p_max_cols in number default NULL, 6 p_max_cols_query in varchar2 default NULL, 7 p_query in varchar2, 8 p_anchor in array, 9 p_pivot in array, 10 p_cursor in out refcursor);

12 end;

Package created. Необходимо задать значения для параметра P_MAX_COLS или для параметра P_MAX_COLS_QUERY. Для создания SQL-оператора необходимо знать количество столбцов в запросе, и эти параметры позволят создать запрос с соответствующим количеством столбцов. Задавать этим параметрам надо значение, полученное в результате выполнения такого запроса: scott@TKYTE816> select max(count(*)) from emp group by deptno, job;

Он возвращает количество различных значений в строках, которые мы хотим транспонировать. Можно либо получить это количество с помощью подобного запроса, либо просто ввести его, если оно заранее известно. Параметр P_QUERY — это запрос, собирающий данные. Для представленного выше примера можно передать следующий запрос: 10 from (select deptno, job, ename, sal, 11 row_number() over (partition by deptno, job 12 order by sal, ename) 13 rn from emp) Следующие два параметра — массивы имен столбцов. Параметр P_ANCHOR указывает, значения каких столбцов остаются в строках, а параметр P_PIVOT перечисляет столбцы, значения которых выносятся в строки. В рассмотренном ранее примере P_ANCHOR = ('DEPTNO', 'JOB'), a P_PIVOT = ('ENAME'.'SAL'). Отвлечемся ненадолго от нашей темы и рассмотрим, как может выглядеть вызов процедуры транспонирования результирующего множества: scott@TKYTE816> variable x refcursor scott@TKYTE816> set autoprint on Аналитические функции scott@TKYTE816> begin 2 my_pkg.pivot 3 (p_max_cols_query => 'select max(count(*)) from emp 4 group by deptno,job', 5 p_query => 'select deptno, job, ename, sal, 6 row_number() over (partition by deptno, job 7 order by sal, ename) 8 rn from emp a', 9 10 p_anchor => my_pkg.array('DEPTNO','JOB'), 11 p_pivot => my_pkg.array ('ENAME', 'SAL'), 12 p_cursor => : x ) ;

13 end;

PL/SQL procedure successfully completed. DEPTNO JOB 10 10 10 20 20 20 30 30 30 CLERK MANAGER PRESIDENT ANALYST CLERK MANAGER CLERK MANAGER SALESMAN ENAME_ SAL_1 ENAME_2 SAL_2 ENAME_3 SAL_3 ENAME_ SAL_4 MILLER CLARK KING FORD SMITH JONES JAMES BLAKE ALLEN 1300 2450 5000 3000 SCOTT 800 ADAMS 2975 99 99 99 MARTIN 3000 99 TURNER 99 WARD 9 rows selected. Как видите, запрос динамически переписан на базе разработанного универсального шаблона. Реализация тела пакета достаточно проста: scott@TKYTE816> create or replace package body my_pkg 2 as 3 4 procedure pivot(p_max_cols in number default null, 5 p_max_cols_query in varchar2 default null, 6 p_query in varchar2, 7 p_anchor in array, 8 p_pivot in array, 9 p_cursor in out refcursor) 10 as 11 l_max_cols number;

12 l_query long;

13 l_cnames array;

14 begin 15 -- определяем количество столбцов, которые надо возвращать 16 -- мы либо ЗНАЕМ его, либо получаем запрос, с помощью которого его -- можно узнать 17 if (p_max_cols is not null) 18 then 19 l_max_cols := p_max_cols;

20 elsif (p_max_cols_query is not null) 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 Глава then execute Immediate p_max_cols_query into l_max_cols;

else raise_application_error(-20001, 'He могу определить максимальное количество столбцов');

end if;

-- Теперь создаем запрос, который позволяет ответит на -- поставленный вопрос... -- Начинаем со столбцов Cl, C2,... СХ: l_query := 'select ';

for i in 1.. p_anchor.count loop l_query := l_query || p_anchor(i) || ',';

end loop;

-- Теперь добавляем транспонируемые столбцы С{х+1}... CN:

-- Формат: "max(decode(rn,l,C{X+l},null)) сх+1_1" for i in 1.. l_max_cols loop for j in 1.. p_pivot.count loop l_query := l_query || 'max(decode(rn,'Hill','|| p_pivot(j)||',null)) ' || p_pivot(j) || '_' || i || ',';

end loop;

end loop;

-- Теперь просто добавляем исходный запрос l_query := rtrim(l_query,',') || ' from (' || p_query || ') group by ' ;

-- а затем — столбцы, по которым надо группировать... for i in 1.. p_anchor.count loop l_query := l_query || p_anchor(i) || ',';

end loop;

l_query := rtrim(l_query,',');

-- и возвращаем курсор для результирующего множества execute immediate 'alter session set cursor_sharing=force';

open p_cursor for l_query;

execute immediate 'alter session set cursor_sharing=exact';

end;

Аналитические функции 70 71 end;

72 / Package body created. Понадобилось несколько строковых функций для перезаписи запроса и динамическое открытие курсорной переменной (REF CURSOR). Поскольку вполне вероятно, что в условии запроса есть константы, мы включаем опцию cursor_sharing перед анализом запроса, чтобы принудительно использовались связываемые переменные, а затем отключаем ее. (Подробнее об этом см. в главе 10, посвященной настройке производительности). В результате получаем полностью проанализированный запрос, готовый для извлечения данных через курсорную переменную.

Доступ к строкам вокруг текущей строки Часто необходимо обращаться к данным не только в текущей строке, но и в ближайших предыдущих или последующих. Предположим, необходимо создать отчет, в котором по отделам были бы представлены все сотрудники, причем, для каждого сотрудника выдана дата его приема на работу, за сколько дней до этой даты последний раз принимали сотрудника на работу, и через сколько дней после этого приняли на работу следующего сотрудника. Написание подобного запроса с помощью "чистого" языка SQL — чрезвычайно сложная задача. Более того, производительность полученного запроса вызывает сомнения. В прошлом я либо пытался применять прием "select из select", либо писал PL/SQL-функцию, которая по данным из текущей строки находила предыдущую и следующую строки данных. Это работало, но очень много времени уходило на разработку запроса (приходилось писать больше кода);

кроме того, расходовалось большое количество ресурсов при его выполнении. С помощью аналитических функций это делается быстро и эффективно. Соответствующий запрос будет выглядеть так: scott@TKYTE816> select deptno, ename, hiredate, 2 lag(hiredate, 1, null) over (partition by deptno 3 order by hiredate, ename) last_hire, 4 hiredate — lag(hiredate, 1, null) 5 over (partition by deptno 6 order by hiredate, ename) days_last, 7 lead(hiredate, 1, null) 8 over (partition by deptno 9 order by hiredate, ename) next_hire, 10 lead(hiredate, 1, null) 11 over (partition by deptno 12 order by hiredate, ename) — hiredate days_next 13 from emp 14 order by deptno, hiredate 15 / DEPTNO ENAME 10 CLARK KING HIREDATE LAST_HIRE DAYS_LAST NEXT_HIRE DAYS_NEXT 09-JUN-81 17-NOV-81 09-JUN-81 17-NOV-81 23-JAN-82 161 1078 Глава MILLER 20 SMITH JONES FORD SCOTT ADAMS 30 ALLEN WARD BLAKE TURNER MARTIN JAMES 14 rows selected. Функции LEAD и LAG можно рассматривать как способы индексации в пределах группы. С помощью этих функций можно обратиться к любой отдельной строке. Обратите внимание: в представленных выше результатах запись для сотрудника KING включает данные (выделены полужирным) из предыдущей строки (LAST_HIRE) и последующей (NEXT_HIRE). Можно получить поля предыдущих или последующих записей в упорядоченном фрагменте. Прежде чем подробно рассматривать функции LAG и LEAD, я хотел бы сравнить этот запрос с аналогичным по результатам запросом, в котором не используются аналитические функции. Для этого я создам необходимые индексы по таблице, чтобы максимально быстро получать ответ: scott@TKYTE816> create table t 2 as 3 select object_name ename, 4 created hiredate, 5 mod(object_id,50) deptno 6 from all_objects 7/ Table created. scott@TKYTE816> alter table t modify deptno not null;

Table altered. scott@TKYTE816> create index t_idx on t(deptno,hiredate,ename) 2/ Index created. scott@TKYTE816> analyze table t 2 compute statistics 3 for table 4 for all indexes 5 for all indexed columns 6/ Table analyzed. 23-JAN-82 17-NOV-81 17-DEC-80 02-APR-81 17-DEC-80 03-DEC-81 02-APR-81 09-DEC-82 03-DEC-81 12-JAN-83 09-DEC-82 20-FEB-81 22-FEB-81 01-MAY-81 08-SEP-81 28-SEP-81 03-DEC-81 20-FEB-81 22-FEB-81 01-MAY-81 08-SEP-81 28-SEP-81 67 02-APR-81 106 03-DEC-81 245 09-DEC-82 371 12-JAN-83 34 2 68 130 20 66 22-FEB-81 01-MAY-81 08-SEP-81 28-SEP-81 03-DEC-81 106 245 371 34 2 68 130 20 Аналитические функции Я даже добавил в индекс столбец ENAME, чтобы при выполнении запроса достаточно было обращения только к индексу, без обращения к таблице по значению ROWID. Запрос с аналитической функцией демонстрирует следующую производительность: scott@TKYTE816> select deptno, ename, hiredate, 2 lag(hiredate, 1, null) over (partition by deptno 3 order by hiredate, ename) last_hire, 4 hiredate — lag(hiredate, 1, null) 5 over (partition by deptno 6 order by hiredate, ename) days_last, 7 lead(hiredate, 1, null) 8 over (partition by deptno 9 order by hiredate, ename) next_hire, 10 lead(hiredate, 1, null) 11 over (partition by deptno 12 order by hiredate, ename) — hiredate days_next 13 from emp 14 order by deptno, hiredate 15 / call Parse Execute2 Fetch total count 1 0.00 1313 1316 cpu 0.01 0.00 0.72 0.73 elapsed 0.01 0 1.57 1.58 disk 0 0 142 142 query 0 0 133 133 current 0 0 2 2 rows 0 19675 Misses in library cache during parse: 0 Optimizer goal: FIRST_ROWS Parsing user id: 54 Rows 19675 19675 Row Source Operation WINDOW BUFFER INDEX FULL SCAN (object id 27899) Сравним с эквивалентным запросом, где аналитические функции не используются: scott@TKYTE816> select deptno, ename, hiredate, 2 hiredate-(select max(hiredate) 3 from t e2 4 where e2.deptno = e1.deptno 5 and e2.hiredate < e1.hiredate) last_hire, 6 hiredate-(select max(hiredate) 7 from t e2 8 where e2.deptno = e1.deptno 9 and e2.hiredate < e1.hiredate) days_last, 10 (select man(hiredate) 11 from t e3 12 where e3.deptno = e1.deptno 13 and e3.hiredate > e1.hiredate) next_hire, 14 (select min(hiredate) 15 from t e3 16 where e3.deptno = e1.deptno 1080 Глава 17 and е3.hiredate > e1.hiredate) — hiredate days_next 18 from t e1 19 order by deptno, hiredate 20 / call Parse Execute Fetch total count 1 1 1313 1315 cpu 0.01 0.00 2.48 2.49 elapsed 0.01 0.00 2.69 2.70 disk 0 0 0 0 query 0 0 141851 141851 current 0 0 0 0 rows 0 0 19675 Misses in library cache during parse: 0 Optimizer goal: FIRST_ROWS Parsing user id: 54 Rows Row Source Operation 19675 INDEX FULL SCAN (object id 27899) Производительность этих двух запросов существенно отличается. Сравните: 135 логических операций ввода-вывода и 141000;

0,73 секунды процессорного времени и 2,49. Запрос с аналитической функцией и в этом случае оказался намного эффективнее. Учтите также сложность текста запросов. Мне кажется, запрос с помощью функций LAG и LEAD не только проще написать, но и понять впоследствии, что выбирается. Прием "select из select" — хороший трюк, но такой код сложнее придумать, а при чтении полученного запроса часто очень трудно понять, что он выбирает. Чтобы восстановить логику второго запроса, придется намного больше думать. Теперь давайте более детально рассмотрим функции LAG и LEAD. Эти функции принимают три аргумента: lag(Argl, Arg2, Arg3) • Argl — выражение, которое надо вернуть на основе другой строки. • Arg2 — смещение требуемой строки в группе относительно текущей. Смещение задается как положительное целое число. В случае функции LAG берется соответствующая смещению предыдущая строка, а в случае функции LEAD — следующая. Этот аргумент имеет стандартное значение 1. • Arg3 — возвращаемое значение в том случае, если смещение, заданное аргументом Arg2, выводит за границу группы. Например, первая строка в каждой группе не имеет предыдущей, так что значение функции LAG(..., 1) для этой строки определить нельзя. Можно возвращать стандартное значение NULL или указать значение явно. Следует учитывать, что окна для функций LAG и LEAD не используются — можно задавать конструкции PARTITION BY и ORDER BY, но не ROWS или RANGE. Итак, в нашем примере: 4 5 6 hiredate - lag(hiredate, 1, null) over (partition by deptno order by hiredate, ename) days_last, Аналитические функции функция LAG использовалась для поиска предыдущей строки, поскольку в качестве второго параметра передавалось значение 1 (если предыдущей записи нет, возвращается значение NULL). Мы фрагментировали данные по столбцу DEPTNO, так что каждый отдел просматривается независимо от остальных. Полученный фрагмент мы упорядочили по значению столбца HIREDATE, так что вызов LAG(HIREDATE, 1, NULL) возвращает максимальное значение HIREDATE, меньшее соответствующего значения в текущей строке.

Проблемы С аналитическими функциями у меня почти не было проблем. Они позволяют получать ответ на абсолютно новые запросы намного эффективнее, чем до их появления. Если разобраться в синтаксисе, открываются безграничные возможности. Не часто бывает, когда результат можно получить практически даром, но с аналитическими функциями все обстоит, похоже, именно так. Следует, однако, помнить о четырех потенциальных проблемах.

Аналитические функции в PL/SQL При попытке использования аналитических функций в коде на языке PL/SQL могут возникать ошибки. Если взять простой запрос и поместить его в PL/SQL-блок: scott@TKYTE816> variable x refcursor scott@TKYTE816> set autoprint on scott@TKYTE816> begin 2 open :x for 3 select mgr, ename, 4 row_number() over (partition by mgr 5 order by ename) 6 rn from emp;

7 end;

8/ row_number() over (partition by mgr * ERROR at line 5: ORA-06550: line 5, column 31: PLS-00103: Encountered the symbol " ( " when expecting one of the following:, from into bulk синтаксический анализатор PL/SQL его не воспримет. Анализатор SQL-операторов, используемый в PL/SQL, еще не понимает (в версиях Oracle 8i — прим. научн. ред.) синтаксис вызова аналитических функций. Сталкиваясь с подобными проблемами (есть и другие конструкции, не воспринимаемые синтаксическим анализатором PL/SQL), я использую динамически открываемую курсорную переменную. Реализация показанного выше запроса в этом случае может выглядеть так: scott@TKYTE816> variable x refcursor scott@TKYTE816> set autoprint on 1082 Глава scott@TKYTE816> begin 2 open :x for 3 'select mgr, ename, 4 row_number() over (partition by mgr 5 order by ename) 6 rn from emp';

7 end;

8/ PL/SQL procedure successfully completed. MGR ENAME 7566 FORD 7566 SCOTT 7698 ALLEN 7698 JAMES 7698 MARTIN 7698 TURNER 7698 WARD 7782 MILLER 7788 ADAMS 7839 BLAKE 7839 CLARK 7839 JONES 7902 SMITH KING 14 rows selected. Мы "обманули" синтаксический анализатор PL/SQL, не разрешая ему анализировать конструкции, которые он не понимает, в данном случае — вызов функции ROW_NUMBER(). Для этого достаточно использовать динамически открываемые курсорные переменные. После открытия они работают аналогично обычным курсорам: из них извлекаются данные, потом курсорные переменные закрываются и т.д., но PL/SQLмашина не пытается анализировать операторы ни во время компиляции, ни при выполнении, поэтому можно использовать новые синтаксические конструкции языка SQL. Можно также создать представление на базе запроса с аналитическими функциями, а затем обращаться в PL/SQL-блоке к этому представлению. Например: scott@TKYTE816> create or replace view 2 emp_view 3 as 4 select mgr, ename, 5 row_number() over (partition by mgr 6 order by ename) rn 7 from emp 8/ View created. scott@TKYTE816> begin 2 open :x for 3 select mgr, ename, rn RN 1 2 1 2 3 4 5 1 1 1 2 3 1 Аналитические функции 4 from emp_view;

5 end;

6/ PL/SQL procedure successfully completed. MGR ENAME 7566 FORD 7566 SCOTT RN 1 Аналитические функции в конструкции WHERE Следует учитывать, что аналитические функции применяются по ходу выполнения запроса почти в самом конце (после них обрабатывается только окончательная конструкция ORDER BY). Это означает, что аналитические функции нельзя непосредственно использовать в условиях (т.е. применять в конструкциях WHERE и HAVING). Если необходимо включать данные в результирующее множество на основе результатов аналитической функции, придется использовать подставляемое представление. Аналитические функции могут использоваться только в списке выбора или в конструкции ORDER BY запроса. В этой главе приводилось много примеров использования подставляемых представлений, в частности в разделе, посвященном выбору первых N строк. Например, чтобы найти группу сотрудников каждого отдела с тремя наибольшими зарплатами, мы выполняли следующий запрос: scott@TKYTE816> select * 2 from (select deptno, ename, sal, 3 dense_rank() over (partition by deptno 4 order by sal desc) dr 5 from emp) 6 where dr <= 3 7 order by deptno, sal desc 8/ Поскольку функцию DENSE_RANK нельзя использовать в конструкции where непосредственно, приходится скрывать ее в подставляемом представлении под псевдонимом DR, чтобы в дальнейшем можно было использовать столбец DR в условии для получения необходимых строк. Такой прием часто используется при работе с аналитическими функциями.

Значения NULL и сортировка Значения NULL могут влиять на результат работы аналитических функций, особенно при использовании сортировки по убыванию. По умолчанию значения NULL считаются больше любых других значений. Рассмотрим следующий пример: scott@TKYTE816> select ename, comm from emp order by comm desc;

ENAME SMITH JONES COMM 1084 Глава CLARK BLAKE SCOTT KING JAMES MILLER FORD ADAMS MARTIN WARD ALLEN TURNER 1400 500 300 14 rows selected. Выбрав первые N строк, получим: scott@TKYTE816> select ename, comn, dr 2 from (select ename/ comm, 3 denserankO over (order by comm desc) 4 dr from emp) 5 where dr <= 3 6 order by comm 8/ ENAME SMITH JONES CLARK BLAKE SCOTT KING JAMES MILLER FORD ADAMS MARTIN HARD COM4 DR 1 1 1 1 1 1 1 1 1 1 2 1400 12 rows selected. Хотя формально это верно, но вряд ли соответствует желаемому результату. Значения NULL либо вообще не должны учитываться, либо интерпретироваться как "наименьшие" в данном случае. Поэтому надо либо исключить значения NULL из рассмотрения, добавив условие where comm is not null: scott@TKYTE816> select ename, comm, dr 2 from (select ename, comm, 3 dense_rank() over (order by comm desc) 4 dr from emp 5 where comm is not null) 6 where dr <= 3 7 order by comm desc 8/ Аналитические функции ENAME MARTIN HARD ALLEN COMM 1400 500 OR 1 2 либо использовать NULLS LAST в конструкции ORDER BY: scott@TKYTE816> select ename, comm, dr 2 from (select ename, comm, 3 dense_rank() over (order by comm desc nulls last) 4 dr from emp 5 where comm is not null) 6 where dr <= 3 7 order by comm desc 8/ ENAME MARTIN WARD ALLEN COM4 1400 500 300 DR 1 2 Следует помнить, что NULLS LAST можно указывать и в обычных конструкциях ORDER BY, а не только при вызове аналитических функций.

Производительность До сих пор все, что удалось узнать об аналитических функциях, свидетельствует о них как об универсальном средстве повышения производительности. Однако при неправильном использовании они могут отрицательно повлиять на производительность. При использовании этих функций надо опасаться видимой легкости, с которой они позволяют сортировать и фильтровать множества немыслимыми в стандартном языке SQL способами. Каждый вызов аналитической функции в списке выбора оператора SELECT может использовать свои фрагменты, окна и порядок сортировки. Если они несовместимы (не являются подмножествами друг друга), может выполняться огромный объем сортировки и фильтрования. Например, ранее мы выполняли следующий запрос: ops$tkyte@DEV816> select ename, deptno, 2 sum(sal) over (order by ename, deptno) sum_ename_deptno, 3 sum(sal) over (order by deptno, ename) sum_deptno_ename 4 from emp 5 order by ename, deptno 6/ В этом запросе имеются три конструкции ORDER BY, то есть может потребоваться три сортировки. Две сортировки можно объединить, поскольку они выполняются по одним и тем же столбцам, но третью — придется выполнять отдельно. Это — не повод для беспокойства или отказа от использования аналитических функций. Просто надо это учитывать. С помощью аналитических функций можно так же легко написать запрос, использующий все ресурсы компьютера, как и запросы, элегантно и эффективно решающие сложные задачи.

Глава Резюме В этой главе мы подробно рассмотрели синтаксис и возможности аналитических функций. Было показано, с какой легкостью они позволяют решать типичные задачи вроде вычисления частичных сумм, транспонирования результирующих множеств, доступа из текущей строки к "соседним" строкам и т.д. Аналитические функции дают широкий спектр потенциальных возможностей для запросов.

Материализованныe представления Материализованные представления — средство повышения производительности для хранилищ данных и систем поддержки принятия решений, которое многократно ускоряет выполнение запросов, обращающихся к большому количеству (сотням тысяч или миллионам) записей. Говоря упрощенно, они позволяют за секунды (и даже доли секунд) выполнять запросы к терабайтам данных. Это достигается за счет прозрачного использования заранее вычисленных итоговых данных и результатов соединений таблиц. Предварительно вычисленные итоговые данные обычно имеют очень небольшой объем по сравнению с исходными данными. Предположим, в компании имеется база данных продаж, в которую загружены сведения о миллионах заказов, и необходимо проанализировать продажи по регионам (весьма типичный запрос). Будут просмотрены все записи, данные — агрегированы по регионам с выполнением необходимых вычислений. С помощью материализованного представления можно сохранить итоговые данные продаж по регионам и обеспечить автоматическую поддержку этих данных системой. При наличии десяти регионов продаж итоговые данные будут состоять из десяти записей, так что мы будем обращаться не к миллиону фактических записей, а только к десяти. Более того, при выполнении несколько измененного запроса, например об объеме продаж по определенному региону, ответ на него тоже можно получить по этому материализованному представлению. В этой главе мы разберемся, что такое материализованные представления, их возможности и, самое главное, как они устроены. Я покажу, как обеспечить использование созданного материализованного представления всеми запросами, для которых оно позволяет получить ответ (иногда очевидно, что сервер Oracle мог бы использовать ма 1090 Глава териализованное представление, но не делает этого из-за отсутствия важной информации). В частности, будет: • рассмотрен пример, демонстрирующий возможности материализованных представлений и позволяющий решить, пригодятся ли они вам;

• описаны параметры и привилегии, которые необходимо установить для использования материализованных представлений;

• показано на примерах использование измерений (dimensions) и требований целостности, позволяющих серверу определить, когда для ответа на запрос оправданно применение материализованного представления;

• описано использование пакета DBMS_OLAP для анализа представлений;

• и в завершение описаны две проблемы, которые надо учитывать при использовании материализованных представлений.

Предыстория Управление итоговыми таблицами — еще одна возможность материализованного представления — уже некоторое время использовалось в инструментальных средствах типа Oracle Discoverer (средство построения произвольных запросов и отчетов). С помощью Discoverer администратор создает в базе данных итоговые таблицы. Затем это инструментальное средство анализировало запросы, прежде чем отправлять их на сервер Oracle. Если имеется итоговая таблица, позволяющая более эффективно ответить на запрос, Discoverer переписывает запрос так, чтобы он обращался к итоговым таблицам, а не к базовым, заданным в исходном запросе, после чего отправляет запрос серверу Oracle. Это было замечательно, пока для выполнения запросов использовалось именно это средство. Если аналогичный запрос выполнялся из среды SQL*Plus или поступал от клиента по протоколу JDBC, то перезапись запроса не происходила (не могла происходить). Более того, синхронизация между исходными и итоговыми данными не могла выполняться автоматически, поскольку это инструментальное средство не входило в состав сервера. Начиная с версии 7.0, сервер Oracle уже поддерживал возможность, аналогичную итоговым таблицам, — моментальные снимки (snapshot). Первоначально эта возможность создавалась для поддержки репликации, но я лично использовал ее для сохранения ответов на большие запросы. Я создавал моментальные снимки, не использующие связь базы данных для репликации данных из одной базы в другую, а просто вычисляющие итоговые значения или выполняющие соединения для часто используемых данных. Это было здорово, но без возможности переписать запросы использование этого приема было ограниченным. Приложение должно было "знать" о существовании итоговых таблиц и использовать их, а это усложняло его создание и поддержку. При добавлении новой итоговой таблицы мне приходилось находить и переписывать код, в котором можно было ее использовать. В версию Oracle 8.1.5 (Enterprise и Personal Edition) из инструментальных средств типа Discoverer была перенесена возможность переписывания запросов, предусмотрены механизмы автоматического обновления и планирования моментальных снимков (что позволило сделать итоговые таблицы "самоподдерживаемыми"), и все это объединено с воз Материализованные представления можностью оптимизатора находить лучший план из многих альтернативных. В результате получились материализованные представления. Все эти возможности, сосредоточенные в сервере, теперь позволяли любому приложению использовать преимущества автоматического переписывания запросов, независимо от способа доступа к базе данных: из утилиты SQL*Plus, из приложения Oracle Forms, через протоколы JDBC и ODBC, из программ Рго*С, OCI или из средства сторонних производителей. Любой сервер Oracle 8i масштаба предприятия позволяет управлять итоговыми таблицами. Кроме того, поскольку все происходит в базе данных, итоговые таблицы легко синхронизировать с исходными (по крайней мере сервер всегда "знает", когда они не синхронизированы, и может не использовать "устаревшие" итоговые таблицы (этим управляет пользователь)). Поскольку эти функциональные возможности включены непосредственно в сервер, любое приложение, способное обратиться к СУБД Oracle, может воспользоваться ими.

Тот же подход лежит в основе реализации средств тщательного контроля доступа (Fine Grained Access Control, FGAC, см. раздел об открытости в главе 1 и главу 21, посвященную этим средствам). Чем "ближе" к данным применяются функции, тем большее количество инструментальных средств сможет ими воспользоваться. Если поместить средства защиты вне базы данных, например в приложении, воспользоваться ими смогут только пользователи этого приложения (и то, если обращаются к данным исключительно через приложение).

Что необходимо для выполнения примеров Для выполнения примеров, представленных в этой главе, необходима редакция Personal или Enterprise Edition сервера версии Oracle 8.1.5 и выше. Соответствующие функциональные возможности не поддерживаются в редакции Standard. Учетная запись, от имени которой будут выполняться примеры, должна иметь следующие привилегии:

• • • • GRANT CREATE SESSION GRANT CREATE TABLE GRANT CREATE MATERIALIZED VIEW GRANT QUERY REWRITE Первые три привилегии можно предоставить роли, которая, в свою очередь, предоставлена учетной записи. Привилегия QUERY REWRITE должна быть предоставлена непосредственно. Кроме того, необходим доступ к табличному пространству, 30—50 Мбайт которого свободно. Наконец, чтобы воспользоваться средствами переписывания запросов, необходимо использовать оптимизатор, основанный на стоимости (Cost-Based Optimizer — СВО). Если СВО не используется, запрос не может быть переписан. В наших примерах остав 1092 Глава лена стандартная цель оптимизации, CHOOSE;

чтобы воспользоваться возможностями переписывания запросов, достаточно проанализировать таблицы.

Пример Продемонстрирую на простом примере, что может дать материализованное представление. Вы увидите, насколько сокращается время выполнения запроса за счет добавления итоговых данных. Запрос к большой таблице будет переписан сервером в запрос к гораздо меньшей таблице, причем без потери точности результата. Начнем с создания большой таблицы, содержащей список владельцев и принадлежащих им объектов. Таблица строится на основе представления ALL_OBJECTS словаря данных: tkyte@TKYTE816> create table my_all_objects 2 nologging 3 as 4 select * from all_objects 5 union all 6 select * from all_objects 7 union all 8 select * from all_objects 9/ Table created. tkyte@TKYTE816> insert /*+ APPEND */ into my_all_objects 2 select * from my_all_objects;

65742 rows created. tkyte@TKYTE816> commit;

Commit complete. tkyte@TKYTE816> insert /*+ APPEND */ into my_all_objects 2 select * from my_all_objects;

131484 rows created. tkyte@TKYTE816> commit;

Conmit complete. tkyte@TKYTE816> analyze table my_all_objects compute statistics;

Table analyzed. Моя система поддерживает язык Java (Java option), поэтому в таблице MY_ALL_OBJECTS после выполнения указанных действий оказалось около 250000 строк. Для получения такого же результата вам, возможно, придется выполнить UNION ALL и INSERT большее количество раз. Теперь выполним запрос к этой таблице, показывающий количество объектов у каждого пользователя. Первоначально для этого понадобится полный просмотр громадной таблицы: tkyte@TKYTE816> set autotrace on tkyte@TKYTE816> set timing on tkyte@TKYTE816> select owner, count(*) from my_all_objects group by owner;

Материализованные представления OWNER A В CTXSYS DBSNMP DEMO DEMO11 DEMO_DDL MDSYS MV_USER ORDPLUGINS ORDSYS OUR_TYPES OUTLN PERFSTAT PUBLIC SCHEDULER SCOTT SEAPARK SYS SYSTEM TESTING TKYTE TTS_USER TYPES 24 rows selected. Elapsed: 00:00:03. COUNT(*) 36 24 2220 48 60 36 108 2112 60 312 2472 12 60 636 117972 36 84 36 135648 624 276 12 48 tkyte@TKYTE816> set timing off tkyte@TKYTE816> set autotrace traceonly tkyte@TKYTE816> select owner, count(*) from my_all_objects group by owner;

Pages:     | 1 |   ...   | 10 | 11 || 13 | 14 |   ...   | 24 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.